Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/Java/Openjdk/test/jdk/java/net/httpclient/   (Sun/Oracle ©)  Datei vom 13.11.2022 mit Größe 75 kB image not shown  

Quelle  DigestEchoServer.java   Sprache: JAVA

 
/*
 * Copyright (c) 2018, 2021, 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 com.sun.net.httpserver.BasicAuthenticator;
import com.sun.net.httpserver.HttpServer;
import com.sun.net.httpserver.HttpsConfigurator;
import com.sun.net.httpserver.HttpsParameters;
import com.sun.net.httpserver.HttpsServer;

import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Writer;
import java.math.BigInteger;
import java.net.Authenticator;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.PasswordAuthentication;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.StandardSocketOptions;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.HexFormat;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.Random;
import java.util.StringTokenizer;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.net.ssl.SSLContext;
import sun.net.www.HeaderParser;
import java.net.http.HttpClient.Version;

/**
 * A simple HTTP server that supports Basic or Digest authentication.
 * By default this server will echo back whatever is present
 * in the request body. Note that the Digest authentication is
 * a test implementation implemented only for tests purposes.
 * @author danielfuchs
 */

public abstract class DigestEchoServer implements HttpServerAdapters {

    public static final boolean DEBUG =
            Boolean.parseBoolean(System.getProperty("test.debug""false"));
    public static final boolean NO_LINGER =
            Boolean.parseBoolean(System.getProperty("test.nolinger""false"));
    public static final boolean TUNNEL_REQUIRES_HOST =
            Boolean.parseBoolean(System.getProperty("test.requiresHost""false"));
    public enum HttpAuthType {
        SERVER, PROXY, SERVER307, PROXY305
        /* add PROXY_AND_SERVER and SERVER_PROXY_NONE */
    };
    public enum HttpAuthSchemeType { NONE, BASICSERVER, BASIC, DIGEST };
    public static final HttpAuthType DEFAULT_HTTP_AUTH_TYPE = HttpAuthType.SERVER;
    public static final String DEFAULT_PROTOCOL_TYPE = "https";
    public static final HttpAuthSchemeType DEFAULT_SCHEME_TYPE = HttpAuthSchemeType.DIGEST;

    public static class HttpTestAuthenticator extends Authenticator {
        private final String realm;
        private final String username;
        // Used to prevent incrementation of 'count' when calling the
        // authenticator from the server side.
        private final ThreadLocal<Boolean> skipCount = new ThreadLocal<>();
        // count will be incremented every time getPasswordAuthentication()
        // is called from the client side.
        final AtomicInteger count = new AtomicInteger();

        public HttpTestAuthenticator(String realm, String username) {
            this.realm = realm;
            this.username = username;
        }
        @Override
        protected PasswordAuthentication getPasswordAuthentication() {
            if (skipCount.get() == null || skipCount.get().booleanValue() == false) {
                System.out.println("Authenticator called: " + count.incrementAndGet());
            }
            return new PasswordAuthentication(getUserName(),
                    new char[] {'d','e','n''t'});
        }
        // Called by the server side to get the password of the user
        // being authentified.
        public final char[] getPassword(String user) {
            if (user.equals(username)) {
                skipCount.set(Boolean.TRUE);
                try {
                    return getPasswordAuthentication().getPassword();
                } finally {
                    skipCount.set(Boolean.FALSE);
                }
            }
            throw new SecurityException("User unknown: " + user);
        }
        public final String getUserName() {
            return username;
        }
        public final String getRealm() {
            return realm;
        }
    }

    public static final HttpTestAuthenticator AUTHENTICATOR;
    static {
        AUTHENTICATOR = new HttpTestAuthenticator("earth""arthur");
    }


    final HttpTestServer       serverImpl; // this server endpoint
    final DigestEchoServer     redirect;   // the target server where to redirect 3xx
    final HttpTestHandler      delegate;   // unused
    final String               key;

    DigestEchoServer(String key,
                             HttpTestServer server,
                             DigestEchoServer target,
                             HttpTestHandler delegate) {
        this.key = key;
        this.serverImpl = server;
        this.redirect = target;
        this.delegate = delegate;
    }

    public static void main(String[] args)
            throws IOException {

        DigestEchoServer server = create(Version.HTTP_1_1,
                DEFAULT_PROTOCOL_TYPE,
                DEFAULT_HTTP_AUTH_TYPE,
                AUTHENTICATOR,
                DEFAULT_SCHEME_TYPE);
        try {
            System.out.println("Server created at " + server.getAddress());
            System.out.println("Strike to exit");
            System.in.read();
        } finally {
            System.out.println("stopping server");
            server.stop();
        }
    }

    private static String toString(HttpTestRequestHeaders headers) {
        return headers.entrySet().stream()
                .map((e) -> e.getKey() + ": " + e.getValue())
                .collect(Collectors.joining("\n"));
    }

    public static DigestEchoServer create(Version version,
                                          String protocol,
                                          HttpAuthType authType,
                                          HttpAuthSchemeType schemeType)
            throws IOException {
        return create(version, protocol, authType, AUTHENTICATOR, schemeType);
    }

    public static DigestEchoServer create(Version version,
                                          String protocol,
                                          HttpAuthType authType,
                                          HttpTestAuthenticator auth,
                                          HttpAuthSchemeType schemeType)
            throws IOException {
        return create(version, protocol, authType, auth, schemeType, null);
    }

    public static DigestEchoServer create(Version version,
                                        String protocol,
                                        HttpAuthType authType,
                                        HttpTestAuthenticator auth,
                                        HttpAuthSchemeType schemeType,
                                        HttpTestHandler delegate)
            throws IOException {
        Objects.requireNonNull(authType);
        Objects.requireNonNull(auth);
        switch(authType) {
            // A server that performs Server Digest authentication.
            case SERVER: return createServer(version, protocol, authType, auth,
                                             schemeType, delegate, "/");
            // A server that pretends to be a Proxy and performs
            // Proxy Digest authentication. If protocol is HTTPS,
            // then this will create a HttpsProxyTunnel that will
            // handle the CONNECT request for tunneling.
            case PROXY: return createProxy(version, protocol, authType, auth,
                                           schemeType, delegate, "/");
            // A server that sends 307 redirect to a server that performs
            // Digest authentication.
            // Note: 301 doesn't work here because it transforms POST into GET.
            case SERVER307: return createServerAndRedirect(version,
                                                        protocol,
                                                        HttpAuthType.SERVER,
                                                        auth, schemeType,
                                                        delegate, 307);
            // A server that sends 305 redirect to a proxy that performs
            // Digest authentication.
            // Note: this is not correctly stubbed/implemented in this test.
            case PROXY305:  return createServerAndRedirect(version,
                                                        protocol,
                                                        HttpAuthType.PROXY,
                                                        auth, schemeType,
                                                        delegate, 305);
            default:
                throw new InternalError("Unknown server type: " + authType);
        }
    }


    /**
     * The SocketBindableFactory ensures that the local port used by an HttpServer
     * or a proxy ServerSocket previously created by the current test/VM will not
     * get reused by a subsequent test in the same VM.
     * This is to avoid having the test client trying to reuse cached connections.
     */

    private static abstract class SocketBindableFactory<B> {
        private static final int MAX = 10;
        private static final CopyOnWriteArrayList<String> addresses =
                new CopyOnWriteArrayList<>();
        protected B createInternal() throws IOException {
            final int max = addresses.size() + MAX;
            final List<B> toClose = new ArrayList<>();
            try {
                for (int i = 1; i <= max; i++) {
                    B bindable = createBindable();
                    InetSocketAddress address = getAddress(bindable);
                    String key = "localhost:" + address.getPort();
                    if (addresses.addIfAbsent(key)) {
                        System.out.println("Socket bound to: " + key
                                + " after " + i + " attempt(s)");
                        return bindable;
                    }
                    System.out.println("warning: address " + key
                            + " already used. Retrying bind.");
                    // keep the port bound until we get a port that we haven't
                    // used already
                    toClose.add(bindable);
                }
            } finally {
                // if we had to retry, then close the socket we're not
                // going to use.
                for (B b : toClose) {
                    try { close(b); } catch (Exception x) { /* ignore */ }
                }
            }
            throw new IOException("Couldn't bind socket after " + max + " attempts: "
                    + "addresses used before: " + addresses);
        }

        protected abstract B createBindable() throws IOException;

        protected abstract InetSocketAddress getAddress(B bindable);

        protected abstract void close(B bindable) throws IOException;
    }

    /*
     * Used to create ServerSocket for a proxy.
     */

    private static final class ServerSocketFactory
    extends SocketBindableFactory<ServerSocket> {
        private static final ServerSocketFactory instance = new ServerSocketFactory();

        static ServerSocket create() throws IOException {
            return instance.createInternal();
        }

        @Override
        protected ServerSocket createBindable() throws IOException {
            ServerSocket ss = new ServerSocket();
            ss.setReuseAddress(false);
            ss.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0));
            return ss;
        }

        @Override
        protected InetSocketAddress getAddress(ServerSocket socket) {
            return new InetSocketAddress(socket.getInetAddress(), socket.getLocalPort());
        }

        @Override
        protected void close(ServerSocket socket) throws IOException {
            socket.close();
        }
    }

    /*
     * Used to create HttpServer
     */

    private static abstract class H1ServerFactory<S extends HttpServer>
            extends SocketBindableFactory<S> {
        @Override
        protected S createBindable() throws IOException {
            S server = newHttpServer();
            server.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), 0);
            return server;
        }

        @Override
        protected InetSocketAddress getAddress(S server) {
            return server.getAddress();
        }

        @Override
        protected void close(S server) throws IOException {
            server.stop(1);
        }

        /*
         * Returns a HttpServer or a HttpsServer in different subclasses.
         */

        protected abstract S newHttpServer() throws IOException;
    }

    /*
     * Used to create Http2TestServer
     */

    private static abstract class H2ServerFactory<S extends Http2TestServer>
            extends SocketBindableFactory<S> {
        @Override
        protected S createBindable() throws IOException {
            final S server;
            try {
                server = newHttpServer();
            } catch (IOException io) {
                throw io;
            } catch (Exception x) {
                throw new IOException(x);
            }
            return server;
        }

        @Override
        protected InetSocketAddress getAddress(S server) {
            return server.getAddress();
        }

        @Override
        protected void close(S server) throws IOException {
            server.stop();
        }

        /*
         * Returns a HttpServer or a HttpsServer in different subclasses.
         */

        protected abstract S newHttpServer() throws Exception;
    }

    private static final class Http2ServerFactory extends H2ServerFactory<Http2TestServer> {
        private static final Http2ServerFactory instance = new Http2ServerFactory();

        static Http2TestServer create() throws IOException {
            return instance.createInternal();
        }

        @Override
        protected Http2TestServer newHttpServer() throws Exception {
            return new Http2TestServer("localhost"false, 0);
        }
    }

    private static final class Https2ServerFactory extends H2ServerFactory<Http2TestServer> {
        private static final Https2ServerFactory instance = new Https2ServerFactory();

        static Http2TestServer create() throws IOException {
            return instance.createInternal();
        }

        @Override
        protected Http2TestServer newHttpServer() throws Exception {
            return new Http2TestServer("localhost"true, 0);
        }
    }

    private static final class Http1ServerFactory extends H1ServerFactory<HttpServer> {
        private static final Http1ServerFactory instance = new Http1ServerFactory();

        static HttpServer create() throws IOException {
            return instance.createInternal();
        }

        @Override
        protected HttpServer newHttpServer() throws IOException {
            return HttpServer.create();
        }
    }

    private static final class Https1ServerFactory extends H1ServerFactory<HttpsServer> {
        private static final Https1ServerFactory instance = new Https1ServerFactory();

        static HttpsServer create() throws IOException {
            return instance.createInternal();
        }

        @Override
        protected HttpsServer newHttpServer() throws IOException {
            return HttpsServer.create();
        }
    }

    static Http2TestServer createHttp2Server(String protocol) throws IOException {
        final Http2TestServer server;
        if ("http".equalsIgnoreCase(protocol)) {
            server = Http2ServerFactory.create();
        } else if ("https".equalsIgnoreCase(protocol)) {
            server = Https2ServerFactory.create();
        } else {
            throw new InternalError("unsupported protocol: " + protocol);
        }
        return server;
    }

    static HttpTestServer createHttpServer(Version version, String protocol)
            throws IOException
    {
        switch(version) {
            case HTTP_1_1:
                return HttpTestServer.of(createHttp1Server(protocol));
            case HTTP_2:
                return HttpTestServer.of(createHttp2Server(protocol));
            default:
                throw new InternalError("Unexpected version: " + version);
        }
    }

    static HttpServer createHttp1Server(String protocol) throws IOException {
        final HttpServer server;
        if ("http".equalsIgnoreCase(protocol)) {
            server = Http1ServerFactory.create();
        } else if ("https".equalsIgnoreCase(protocol)) {
            server = configure(Https1ServerFactory.create());
        } else {
            throw new InternalError("unsupported protocol: " + protocol);
        }
        return server;
    }

    static HttpsServer configure(HttpsServer server) throws IOException {
        try {
            SSLContext ctx = SSLContext.getDefault();
            server.setHttpsConfigurator(new Configurator(ctx));
        } catch (NoSuchAlgorithmException ex) {
            throw new IOException(ex);
        }
        return server;
    }


    static void setContextAuthenticator(HttpTestContext ctxt,
                                        HttpTestAuthenticator auth) {
        final String realm = auth.getRealm();
        com.sun.net.httpserver.Authenticator authenticator =
            new BasicAuthenticator(realm) {
                @Override
                public boolean checkCredentials(String username, String pwd) {
                    return auth.getUserName().equals(username)
                           && new String(auth.getPassword(username)).equals(pwd);
                }
        };
        ctxt.setAuthenticator(authenticator);
    }

    public static DigestEchoServer createServer(Version version,
                                        String protocol,
                                        HttpAuthType authType,
                                        HttpTestAuthenticator auth,
                                        HttpAuthSchemeType schemeType,
                                        HttpTestHandler delegate,
                                        String path)
            throws IOException {
        Objects.requireNonNull(authType);
        Objects.requireNonNull(auth);

        HttpTestServer impl = createHttpServer(version, protocol);
        String key = String.format("DigestEchoServer[PID=%s,PORT=%s]:%s:%s:%s:%s",
                ProcessHandle.current().pid(),
                impl.getAddress().getPort(),
                version, protocol, authType, schemeType);
        final DigestEchoServer server = new DigestEchoServerImpl(key, impl, null, delegate);
        final HttpTestHandler handler =
                server.createHandler(schemeType, auth, authType, false);
        HttpTestContext context = impl.addHandler(handler, path);
        server.configureAuthentication(context, schemeType, auth, authType);
        impl.start();
        return server;
    }

    public static DigestEchoServer createProxy(Version version,
                                        String protocol,
                                        HttpAuthType authType,
                                        HttpTestAuthenticator auth,
                                        HttpAuthSchemeType schemeType,
                                        HttpTestHandler delegate,
                                        String path)
            throws IOException {
        Objects.requireNonNull(authType);
        Objects.requireNonNull(auth);

        if (version == Version.HTTP_2 && protocol.equalsIgnoreCase("http")) {
            System.out.println("WARNING: can't use HTTP/1.1 proxy with unsecure HTTP/2 server");
            version = Version.HTTP_1_1;
        }
        HttpTestServer impl = createHttpServer(version, protocol);
        String key = String.format("DigestEchoServer[PID=%s,PORT=%s]:%s:%s:%s:%s",
                ProcessHandle.current().pid(),
                impl.getAddress().getPort(),
                version, protocol, authType, schemeType);
        final DigestEchoServer server = "https".equalsIgnoreCase(protocol)
                ? new HttpsProxyTunnel(key, impl, null, delegate)
                : new DigestEchoServerImpl(key, impl, null, delegate);

        final HttpTestHandler hh = server.createHandler(HttpAuthSchemeType.NONE,
                                         null, HttpAuthType.SERVER,
                                         server instanceof HttpsProxyTunnel);
        HttpTestContext ctxt = impl.addHandler(hh, path);
        server.configureAuthentication(ctxt, schemeType, auth, authType);
        impl.start();

        return server;
    }

    public static DigestEchoServer createServerAndRedirect(
                                        Version version,
                                        String protocol,
                                        HttpAuthType targetAuthType,
                                        HttpTestAuthenticator auth,
                                        HttpAuthSchemeType schemeType,
                                        HttpTestHandler targetDelegate,
                                        int code300)
            throws IOException {
        Objects.requireNonNull(targetAuthType);
        Objects.requireNonNull(auth);

        // The connection between client and proxy can only
        // be a plain connection: SSL connection to proxy
        // is not supported by our client connection.
        String targetProtocol = targetAuthType == HttpAuthType.PROXY
                                          ? "http"
                                          : protocol;
        DigestEchoServer redirectTarget =
                (targetAuthType == HttpAuthType.PROXY)
                ? createProxy(version, protocol, targetAuthType,
                              auth, schemeType, targetDelegate, "/")
                : createServer(version, targetProtocol, targetAuthType,
                               auth, schemeType, targetDelegate, "/");
        HttpTestServer impl = createHttpServer(version, protocol);
        String key = String.format("RedirectingServer[PID=%s,PORT=%s]:%s:%s:%s:%s",
                ProcessHandle.current().pid(),
                impl.getAddress().getPort(),
                version, protocol,
                HttpAuthType.SERVER, code300)
                + "->" + redirectTarget.key;
        final DigestEchoServer redirectingServer =
                 new DigestEchoServerImpl(key, impl, redirectTarget, null);
        InetSocketAddress redirectAddr = redirectTarget.getAddress();
        URL locationURL = url(targetProtocol, redirectAddr, "/");
        final HttpTestHandler hh = redirectingServer.create300Handler(key, locationURL,
                                             HttpAuthType.SERVER, code300);
        impl.addHandler(hh,"/");
        impl.start();
        return redirectingServer;
    }

    public abstract InetSocketAddress getServerAddress();
    public abstract InetSocketAddress getProxyAddress();
    public abstract InetSocketAddress getAddress();
    public abstract void stop();
    public abstract Version getServerVersion();

    private static class DigestEchoServerImpl extends DigestEchoServer {
        DigestEchoServerImpl(String key,
                             HttpTestServer server,
                             DigestEchoServer target,
                             HttpTestHandler delegate) {
            super(key, Objects.requireNonNull(server), target, delegate);
        }

        public InetSocketAddress getAddress() {
            return new InetSocketAddress(InetAddress.getLoopbackAddress(),
                    serverImpl.getAddress().getPort());
        }

        public InetSocketAddress getServerAddress() {
            return new InetSocketAddress(InetAddress.getLoopbackAddress(),
                    serverImpl.getAddress().getPort());
        }

        public InetSocketAddress getProxyAddress() {
            return new InetSocketAddress(InetAddress.getLoopbackAddress(),
                    serverImpl.getAddress().getPort());
        }

        public Version getServerVersion() {
            return serverImpl.getVersion();
        }

        public void stop() {
            serverImpl.stop();
            if (redirect != null) {
                redirect.stop();
            }
        }
    }

    protected void writeResponse(HttpTestExchange he) throws IOException {
        if (delegate == null) {
            he.sendResponseHeaders(HttpURLConnection.HTTP_OK, -1);
            he.getResponseBody().write(he.getRequestBody().readAllBytes());
        } else {
            delegate.handle(he);
        }
    }

    private HttpTestHandler createHandler(HttpAuthSchemeType schemeType,
                                      HttpTestAuthenticator auth,
                                      HttpAuthType authType,
                                      boolean tunelled) {
        return new HttpNoAuthHandler(key, authType, tunelled);
    }

    void configureAuthentication(HttpTestContext ctxt,
                                 HttpAuthSchemeType schemeType,
                                 HttpTestAuthenticator auth,
                                 HttpAuthType authType) {
        switch(schemeType) {
            case DIGEST:
                // DIGEST authentication is handled by the handler.
                ctxt.addFilter(new HttpDigestFilter(key, auth, authType));
                break;
            case BASIC:
                // BASIC authentication is handled by the filter.
                ctxt.addFilter(new HttpBasicFilter(key, auth, authType));
                break;
            case BASICSERVER:
                switch(authType) {
                    case PROXY: case PROXY305:
                        // HttpServer can't support Proxy-type authentication
                        // => we do as if BASIC had been specified, and we will
                        //    handle authentication in the handler.
                        ctxt.addFilter(new HttpBasicFilter(key, auth, authType));
                        break;
                    case SERVER: case SERVER307:
                        if (ctxt.getVersion() == Version.HTTP_1_1) {
                            // Basic authentication is handled by HttpServer
                            // directly => the filter should not perform
                            // authentication again.
                            setContextAuthenticator(ctxt, auth);
                            ctxt.addFilter(new HttpNoAuthFilter(key, authType));
                        } else {
                            ctxt.addFilter(new HttpBasicFilter(key, auth, authType));
                        }
                        break;
                    default:
                        throw new InternalError(key + ": Invalid combination scheme="
                             + schemeType + " authType=" + authType);
                }
            case NONE:
                // No authentication at all.
                ctxt.addFilter(new HttpNoAuthFilter(key, authType));
                break;
            default:
                throw new InternalError(key + ": No such scheme: " + schemeType);
        }
    }

    private HttpTestHandler create300Handler(String key, URL proxyURL,
                                             HttpAuthType type, int code300)
            throws MalformedURLException
    {
        return new Http3xxHandler(key, proxyURL, type, code300);
    }

    // Abstract HTTP filter class.
    private abstract static class AbstractHttpFilter extends HttpTestFilter {

        final HttpAuthType authType;
        final String type;
        public AbstractHttpFilter(HttpAuthType authType, String type) {
            this.authType = authType;
            this.type = type;
        }

        String getLocation() {
            return "Location";
        }
        String getAuthenticate() {
            return authType == HttpAuthType.PROXY
                    ? "Proxy-Authenticate" : "WWW-Authenticate";
        }
        String getAuthorization() {
            return authType == HttpAuthType.PROXY
                    ? "Proxy-Authorization" : "Authorization";
        }
        int getUnauthorizedCode() {
            return authType == HttpAuthType.PROXY
                    ? HttpURLConnection.HTTP_PROXY_AUTH
                    : HttpURLConnection.HTTP_UNAUTHORIZED;
        }
        String getKeepAlive() {
            return "keep-alive";
        }
        String getConnection() {
            return authType == HttpAuthType.PROXY
                    ? "Proxy-Connection" : "Connection";
        }
        protected abstract boolean isAuthentified(HttpTestExchange he) throws IOException;
        protected abstract void requestAuthentication(HttpTestExchange he) throws IOException;
        protected void accept(HttpTestExchange he, HttpChain chain) throws IOException {
            chain.doFilter(he);
        }

        @Override
        public String description() {
            return "Filter for " + type;
        }
        @Override
        public void doFilter(HttpTestExchange he, HttpChain chain) throws IOException {
            try {
                System.out.println(type + ": Got " + he.getRequestMethod()
                    + ": " + he.getRequestURI()
                    + "\n" + DigestEchoServer.toString(he.getRequestHeaders()));

                // Assert only a single value for Expect. Not directly related
                // to digest authentication, but verifies good client behaviour.
                List<String> expectValues = he.getRequestHeaders().get("Expect");
                if (expectValues != null && expectValues.size() > 1) {
                    throw new IOException("Expect: " + expectValues);
                }

                if (!isAuthentified(he)) {
                    try {
                        requestAuthentication(he);
                        he.sendResponseHeaders(getUnauthorizedCode(), -1);
                        System.out.println(type
                            + ": Sent back " + getUnauthorizedCode());
                    } finally {
                        he.close();
                    }
                } else {
                    accept(he, chain);
                }
            } catch (RuntimeException | Error | IOException t) {
               System.err.println(type
                    + ": Unexpected exception while handling request: " + t);
               t.printStackTrace(System.err);
               he.close();
               throw t;
            }
        }

    }

    // WARNING: This is not a full fledged implementation of DIGEST.
    // It does contain bugs and inaccuracy.
    final static class DigestResponse {
        final String realm;
        final String username;
        final String nonce;
        final String cnonce;
        final String nc;
        final String uri;
        final String algorithm;
        final String response;
        final String qop;
        final String opaque;

        public DigestResponse(String realm, String username, String nonce,
                              String cnonce, String nc, String uri,
                              String algorithm, String qop, String opaque,
                              String response) {
            this.realm = realm;
            this.username = username;
            this.nonce = nonce;
            this.cnonce = cnonce;
            this.nc = nc;
            this.uri = uri;
            this.algorithm = algorithm;
            this.qop = qop;
            this.opaque = opaque;
            this.response = response;
        }

        String getAlgorithm(String defval) {
            return algorithm == null ? defval : algorithm;
        }
        String getQoP(String defval) {
            return qop == null ? defval : qop;
        }

        // Code stolen from DigestAuthentication:

        private static String encode(String src, char[] passwd, MessageDigest md) {
            try {
                md.update(src.getBytes("ISO-8859-1"));
            } catch (java.io.UnsupportedEncodingException uee) {
                assert false;
            }
            if (passwd != null) {
                byte[] passwdBytes = new byte[passwd.length];
                for (int i=0; i<passwd.length; i++)
                    passwdBytes[i] = (byte)passwd[i];
                md.update(passwdBytes);
                Arrays.fill(passwdBytes, (byte)0x00);
            }
            byte[] digest = md.digest();
            return HexFormat.of().formatHex(digest);
        }

        public static String computeDigest(boolean isRequest,
                                           String reqMethod,
                                           char[] password,
                                           DigestResponse params)
            throws NoSuchAlgorithmException
        {

            String A1, HashA1;
            String algorithm = params.getAlgorithm("MD5");
            boolean md5sess = algorithm.equalsIgnoreCase ("MD5-sess");

            MessageDigest md = MessageDigest.getInstance(md5sess?"MD5":algorithm);

            if (params.username == null) {
                throw new IllegalArgumentException("missing username");
            }
            if (params.realm == null) {
                throw new IllegalArgumentException("missing realm");
            }
            if (params.uri == null) {
                throw new IllegalArgumentException("missing uri");
            }
            if (params.nonce == null) {
                throw new IllegalArgumentException("missing nonce");
            }

            A1 = params.username + ":" + params.realm + ":";
            HashA1 = encode(A1, password, md);

            String A2;
            if (isRequest) {
                A2 = reqMethod + ":" + params.uri;
            } else {
                A2 = ":" + params.uri;
            }
            String HashA2 = encode(A2, null, md);
            String combo, finalHash;

            if ("auth".equals(params.qop)) { /* RRC2617 when qop=auth */
                if (params.cnonce == null) {
                    throw new IllegalArgumentException("missing nonce");
                }
                if (params.nc == null) {
                    throw new IllegalArgumentException("missing nonce");
                }
                combo = HashA1+ ":" + params.nonce + ":" + params.nc + ":" +
                            params.cnonce + ":auth:" +HashA2;

            } else { /* for compatibility with RFC2069 */
                combo = HashA1 + ":" +
                           params.nonce + ":" +
                           HashA2;
            }
            finalHash = encode(combo, null, md);
            return finalHash;
        }

        public static DigestResponse create(String raw) {
            String username, realm, nonce, nc, uri, response, cnonce,
                   algorithm, qop, opaque;
            HeaderParser parser = new HeaderParser(raw);
            username = parser.findValue("username");
            realm = parser.findValue("realm");
            nonce = parser.findValue("nonce");
            nc = parser.findValue("nc");
            uri = parser.findValue("uri");
            cnonce = parser.findValue("cnonce");
            response = parser.findValue("response");
            algorithm = parser.findValue("algorithm");
            qop = parser.findValue("qop");
            opaque = parser.findValue("opaque");
            return new DigestResponse(realm, username, nonce, cnonce, nc, uri,
                                      algorithm, qop, opaque, response);
        }

    }

    private static class HttpNoAuthFilter extends AbstractHttpFilter {

        static String type(String key, HttpAuthType authType) {
            String type = authType == HttpAuthType.SERVER
                    ? "NoAuth Server Filter" : "NoAuth Proxy Filter";
            return "["+type+"]:"+key;
        }

        public HttpNoAuthFilter(String key, HttpAuthType authType) {
            super(authType, type(key, authType));
        }

        @Override
        protected boolean isAuthentified(HttpTestExchange he) throws IOException {
            return true;
        }

        @Override
        protected void requestAuthentication(HttpTestExchange he) throws IOException {
            throw new InternalError("Should not com here");
        }

        @Override
        public String description() {
            return "Passthrough Filter";
        }

    }

    // An HTTP Filter that performs Basic authentication
    private static class HttpBasicFilter extends AbstractHttpFilter {

        static String type(String key, HttpAuthType authType) {
            String type = authType == HttpAuthType.SERVER
                    ? "Basic Server Filter" : "Basic Proxy Filter";
            return "["+type+"]:"+key;
        }

        private final HttpTestAuthenticator auth;
        public HttpBasicFilter(String key, HttpTestAuthenticator auth,
                               HttpAuthType authType) {
            super(authType, type(key, authType));
            this.auth = auth;
        }

        @Override
        protected void requestAuthentication(HttpTestExchange he)
            throws IOException
        {
            String headerName = getAuthenticate();
            String headerValue = "Basic realm=\"" + auth.getRealm() + "\"";
            he.getResponseHeaders().addHeader(headerName, headerValue);
            System.out.println(type + ": Requesting Basic Authentication, "
                               + headerName + " : "+ headerValue);
        }

        @Override
        protected boolean isAuthentified(HttpTestExchange he) {
            if (he.getRequestHeaders().containsKey(getAuthorization())) {
                List<String> authorization =
                    he.getRequestHeaders().get(getAuthorization());
                for (String a : authorization) {
                    System.out.println(type + ": processing " + a);
                    int sp = a.indexOf(' ');
                    if (sp < 0) return false;
                    String scheme = a.substring(0, sp);
                    if (!"Basic".equalsIgnoreCase(scheme)) {
                        System.out.println(type + ": Unsupported scheme '"
                                           + scheme +"'");
                        return false;
                    }
                    if (a.length() <= sp+1) {
                        System.out.println(type + ": value too short for '"
                                            + scheme +"'");
                        return false;
                    }
                    a = a.substring(sp+1);
                    return validate(a);
                }
                return false;
            }
            return false;
        }

        boolean validate(String a) {
            byte[] b = Base64.getDecoder().decode(a);
            String userpass = new String (b);
            int colon = userpass.indexOf (':');
            String uname = userpass.substring (0, colon);
            String pass = userpass.substring (colon+1);
            return auth.getUserName().equals(uname) &&
                   new String(auth.getPassword(uname)).equals(pass);
        }

        @Override
        public String description() {
            return "Filter for BASIC authentication: " + type;
        }

    }


    // An HTTP Filter that performs Digest authentication
    // WARNING: This is not a full fledged implementation of DIGEST.
    // It does contain bugs and inaccuracy.
    private static class HttpDigestFilter extends AbstractHttpFilter {

        static String type(String key, HttpAuthType authType) {
            String type = authType == HttpAuthType.SERVER
                    ? "Digest Server Filter" : "Digest Proxy Filter";
            return "["+type+"]:"+key;
        }

        // This is a very basic DIGEST - used only for the purpose of testing
        // the client implementation. Therefore we can get away with never
        // updating the server nonce as it makes the implementation of the
        // server side digest simpler.
        private final HttpTestAuthenticator auth;
        private final byte[] nonce;
        private final String ns;
        public HttpDigestFilter(String key, HttpTestAuthenticator auth, HttpAuthType authType) {
            super(authType, type(key, authType));
            this.auth = auth;
            nonce = new byte[16];
            new Random(Instant.now().toEpochMilli()).nextBytes(nonce);
            ns = new BigInteger(1, nonce).toString(16);
        }

        @Override
        protected void requestAuthentication(HttpTestExchange he)
                throws IOException {
            String separator;
            Version v = he.getExchangeVersion();
            if (v == Version.HTTP_1_1) {
                separator = "\r\n ";
            } else if (v == Version.HTTP_2) {
                separator = " ";
            } else {
                throw new InternalError(String.valueOf(v));
            }
            String headerName = getAuthenticate();
            String headerValue = "Digest realm=\"" + auth.getRealm() + "\","
                    + separator + "qop=\"auth\","
                    + separator + "nonce=\"" + ns +"\"";
            he.getResponseHeaders().addHeader(headerName, headerValue);
            System.out.println(type + ": Requesting Digest Authentication, "
                               + headerName + " : " + headerValue);
        }

        @Override
        protected boolean isAuthentified(HttpTestExchange he) {
            if (he.getRequestHeaders().containsKey(getAuthorization())) {
                List<String> authorization = he.getRequestHeaders().get(getAuthorization());
                for (String a : authorization) {
                    System.out.println(type + ": processing " + a);
                    int sp = a.indexOf(' ');
                    if (sp < 0) return false;
                    String scheme = a.substring(0, sp);
                    if (!"Digest".equalsIgnoreCase(scheme)) {
                        System.out.println(type + ": Unsupported scheme '" + scheme +"'");
                        return false;
                    }
                    if (a.length() <= sp+1) {
                        System.out.println(type + ": value too short for '" + scheme +"'");
                        return false;
                    }
                    a = a.substring(sp+1);
                    DigestResponse dgr = DigestResponse.create(a);
                    return validate(he.getRequestURI(), he.getRequestMethod(), dgr);
                }
                return false;
            }
            return false;
        }

        boolean validate(URI uri, String reqMethod, DigestResponse dg) {
            if (!"MD5".equalsIgnoreCase(dg.getAlgorithm("MD5"))) {
                System.out.println(type + ": Unsupported algorithm "
                                   + dg.algorithm);
                return false;
            }
            if (!"auth".equalsIgnoreCase(dg.getQoP("auth"))) {
                System.out.println(type + ": Unsupported qop "
                                   + dg.qop);
                return false;
            }
            try {
                if (!dg.nonce.equals(ns)) {
                    System.out.println(type + ": bad nonce returned by client: "
                                    + nonce + " expected " + ns);
                    return false;
                }
                if (dg.response == null) {
                    System.out.println(type + ": missing digest response.");
                    return false;
                }
                char[] pa = auth.getPassword(dg.username);
                return verify(uri, reqMethod, dg, pa);
            } catch(IllegalArgumentException | SecurityException
                    | NoSuchAlgorithmException e) {
                System.out.println(type + ": " + e.getMessage());
                return false;
            }
        }


        boolean verify(URI uri, String reqMethod, DigestResponse dg, char[] pw)
            throws NoSuchAlgorithmException {
            String response = DigestResponse.computeDigest(true, reqMethod, pw, dg);
            if (!dg.response.equals(response)) {
                System.out.println(type + ": bad response returned by client: "
                                    + dg.response + " expected " + response);
                return false;
            } else {
                // A real server would also verify the uri=<request-uri>
                // parameter - but this is just a test...
                System.out.println(type + ": verified response " + response);
            }
            return true;
        }


        @Override
        public String description() {
            return "Filter for DIGEST authentication: " + type;
        }
    }

    // Abstract HTTP handler class.
    private abstract static class AbstractHttpHandler implements HttpTestHandler {

        final HttpAuthType authType;
        final String type;
        public AbstractHttpHandler(HttpAuthType authType, String type) {
            this.authType = authType;
            this.type = type;
        }

        String getLocation() {
            return "Location";
        }

        @Override
        public void handle(HttpTestExchange he) throws IOException {
            try {
                sendResponse(he);
            } catch (RuntimeException | Error | IOException t) {
               System.err.println(type
                    + ": Unexpected exception while handling request: " + t);
               t.printStackTrace(System.err);
               throw t;
            } finally {
                he.close();
            }
        }

        protected abstract void sendResponse(HttpTestExchange he) throws IOException;

    }

    static String stype(String type, String key, HttpAuthType authType, boolean tunnelled) {
        type = type + (authType == HttpAuthType.SERVER
                       ? " Server" : " Proxy")
                + (tunnelled ? " Tunnelled" : "");
        return "["+type+"]:"+key;
    }

    private class HttpNoAuthHandler extends AbstractHttpHandler {

        // true if this server is behind a proxy tunnel.
        final boolean tunnelled;
        public HttpNoAuthHandler(String key, HttpAuthType authType, boolean tunnelled) {
            super(authType, stype("NoAuth", key, authType, tunnelled));
            this.tunnelled = tunnelled;
        }

        @Override
        protected void sendResponse(HttpTestExchange he) throws IOException {
            if (DEBUG) {
                System.out.println(type + ": headers are: "
                        + DigestEchoServer.toString(he.getRequestHeaders()));
            }
            if (authType == HttpAuthType.SERVER && tunnelled) {
                // Verify that the client doesn't send us proxy-* headers
                // used to establish the proxy tunnel
                Optional<String> proxyAuth = he.getRequestHeaders()
                        .keySet().stream()
                        .filter("proxy-authorization"::equalsIgnoreCase)
                        .findAny();
                if (proxyAuth.isPresent()) {
                    System.out.println(type + " found "
                            + proxyAuth.get() + ": failing!");
                    throw new IOException(proxyAuth.get()
                            + " found by " + type + " for "
                            + he.getRequestURI());
                }
            }
            DigestEchoServer.this.writeResponse(he);
        }

    }

    // A dummy HTTP Handler that redirects all incoming requests
    // by sending a back 3xx response code (301, 305, 307 etc..)
    private class Http3xxHandler extends AbstractHttpHandler {

        private final URL redirectTargetURL;
        private final int code3XX;
        public Http3xxHandler(String key, URL proxyURL, HttpAuthType authType, int code300) {
            super(authType, stype("Server" + code300, key, authType, false));
            this.redirectTargetURL = proxyURL;
            this.code3XX = code300;
        }

        int get3XX() {
            return code3XX;
        }

        @Override
        public void sendResponse(HttpTestExchange he) throws IOException {
            System.out.println(type + ": Got " + he.getRequestMethod()
                    + ": " + he.getRequestURI()
                    + "\n" + DigestEchoServer.toString(he.getRequestHeaders()));
            System.out.println(type + ": Redirecting to "
                               + (authType == HttpAuthType.PROXY305
                                    ? "proxy" : "server"));
            he.getResponseHeaders().addHeader(getLocation(),
                redirectTargetURL.toExternalForm().toString());
            he.sendResponseHeaders(get3XX(), -1);
            System.out.println(type + ": Sent back " + get3XX() + " "
                 + getLocation() + ": " + redirectTargetURL.toExternalForm().toString());
        }
    }

    static class Configurator extends HttpsConfigurator {
        public Configurator(SSLContext ctx) {
            super(ctx);
        }

        @Override
        public void configure (HttpsParameters params) {
            params.setSSLParameters (getSSLContext().getSupportedSSLParameters());
        }
    }

    static final long start = System.nanoTime();
    public static String now() {
        long now = System.nanoTime() - start;
        long secs = now / 1000_000_000;
        long mill = (now % 1000_000_000) / 1000_000;
        long nan = now % 1000_000;
        return String.format("[%d s, %d ms, %d ns] ", secs, mill, nan);
    }

    static class  ProxyAuthorization {
        final HttpAuthSchemeType schemeType;
        final HttpTestAuthenticator authenticator;
        private final byte[] nonce;
        private final String ns;
        private final String key;

        ProxyAuthorization(String key, HttpAuthSchemeType schemeType, HttpTestAuthenticator auth) {
            this.key = key;
            this.schemeType = schemeType;
            this.authenticator = auth;
            nonce = new byte[16];
            new Random(Instant.now().toEpochMilli()).nextBytes(nonce);
            ns = new BigInteger(1, nonce).toString(16);
        }

        String doBasic(Optional<String> authorization) {
            String offset = "proxy-authorization: basic ";
            String authstring = authorization.orElse("");
            if (!authstring.toLowerCase(Locale.US).startsWith(offset)) {
                return "Proxy-Authenticate: BASIC " + "realm=\""
                        + authenticator.getRealm() +"\"";
            }
            authstring = authstring
                    .substring(offset.length())
                    .trim();
            byte[] base64 = Base64.getDecoder().decode(authstring);
            String up = new String(base64, StandardCharsets.UTF_8);
            int colon = up.indexOf(':');
            if (colon < 1) {
                return "Proxy-Authenticate: BASIC " + "realm=\""
                        + authenticator.getRealm() +"\"";
            }
            String u = up.substring(0, colon);
            String p = up.substring(colon+1);
            char[] pw = authenticator.getPassword(u);
            if (!p.equals(new String(pw))) {
                return "Proxy-Authenticate: BASIC " + "realm=\""
                        + authenticator.getRealm() +"\"";
            }
            System.out.println(now() + key + " Proxy basic authentication success");
            return null;
        }

        String doDigest(Optional<String> authorization) {
            String offset = "proxy-authorization: digest ";
            String authstring = authorization.orElse("");
            if (!authstring.toLowerCase(Locale.US).startsWith(offset)) {
                return "Proxy-Authenticate: " +
                        "Digest realm=\"" + authenticator.getRealm() + "\","
                        + "\r\n qop=\"auth\","
                        + "\r\n nonce=\"" + ns +"\"";
            }
            authstring = authstring
                    .substring(offset.length())
                    .trim();
            boolean validated = false;
            try {
                DigestResponse dgr = DigestResponse.create(authstring);
                validated = validate("CONNECT", dgr);
            } catch (Throwable t) {
                t.printStackTrace();
            }
            if (!validated) {
                return "Proxy-Authenticate: " +
                        "Digest realm=\"" + authenticator.getRealm() + "\","
                        + "\r\n qop=\"auth\","
                        + "\r\n nonce=\"" + ns +"\"";
            }
            return null;
        }




        boolean validate(String reqMethod, DigestResponse dg) {
            String type = now() + this.getClass().getSimpleName() + ":" + key;
            if (!"MD5".equalsIgnoreCase(dg.getAlgorithm("MD5"))) {
                System.out.println(type + ": Unsupported algorithm "
                        + dg.algorithm);
                return false;
            }
            if (!"auth".equalsIgnoreCase(dg.getQoP("auth"))) {
                System.out.println(type + ": Unsupported qop "
                        + dg.qop);
                return false;
            }
            try {
                if (!dg.nonce.equals(ns)) {
                    System.out.println(type + ": bad nonce returned by client: "
                            + nonce + " expected " + ns);
                    return false;
                }
                if (dg.response == null) {
                    System.out.println(type + ": missing digest response.");
                    return false;
                }
                char[] pa = authenticator.getPassword(dg.username);
                return verify(type, reqMethod, dg, pa);
            } catch(IllegalArgumentException | SecurityException
                    | NoSuchAlgorithmException e) {
                System.out.println(type + ": " + e.getMessage());
                return false;
            }
        }


        boolean verify(String type, String reqMethod, DigestResponse dg, char[] pw)
                throws NoSuchAlgorithmException {
            String response = DigestResponse.computeDigest(true, reqMethod, pw, dg);
            if (!dg.response.equals(response)) {
                System.out.println(type + ": bad response returned by client: "
                        + dg.response + " expected " + response);
                return false;
            } else {
                // A real server would also verify the uri=<request-uri>
                // parameter - but this is just a test...
                System.out.println(type + ": verified response " + response);
            }
            return true;
        }

        public boolean authorize(StringBuilder response, String requestLine, String headers) {
            String message = "

Authorization Failed%s

\r\n"
;
            if (authenticator == null && schemeType != HttpAuthSchemeType.NONE) {
                message = String.format(message, " No Authenticator Set");
                response.append("HTTP/1.1 407 Proxy Authentication Failed\r\n");
                response.append("Content-Length: ")
                        .append(message.getBytes(StandardCharsets.UTF_8).length)
                        .append("\r\n\r\n");
                response.append(message);
                return false;
            }
            Optional<String> authorization = Stream.of(headers.split("\r\n"))
                    .filter((k) -> k.toLowerCase(Locale.US).startsWith("proxy-authorization:"))
                    .findFirst();
            String authenticate = null;
            switch(schemeType) {
                case BASIC:
                case BASICSERVER:
                    authenticate = doBasic(authorization);
                    break;
                case DIGEST:
                    authenticate = doDigest(authorization);
                    break;
                case NONE:
                    response.append("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
                    return true;
                default:
                    throw new InternalError("Unknown scheme type: " + schemeType);
            }
            if (authenticate != null) {
                message = String.format(message, "");
                response.append("HTTP/1.1 407 Proxy Authentication Required\r\n");
                response.append("Content-Length: ")
                        .append(message.getBytes(StandardCharsets.UTF_8).length)
                        .append("\r\n")
                        .append(authenticate)
                        .append("\r\n\r\n");
                response.append(message);
                return false;
            }
            response.append("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
            return true;
        }
    }

    public interface TunnelingProxy {
        InetSocketAddress getProxyAddress();
        void stop();
    }

    // This is a bit hacky: HttpsProxyTunnel is an HTTPTestServer hidden
    // behind a fake proxy that only understands CONNECT requests.
    // The fake proxy is just a server socket that intercept the
    // CONNECT and then redirect streams to the real server.
    static class HttpsProxyTunnel extends DigestEchoServer
            implements Runnable, TunnelingProxy {

        final ServerSocket ss;
        final CopyOnWriteArrayList<CompletableFuture<Void>> connectionCFs
                = new CopyOnWriteArrayList<>();
        volatile ProxyAuthorization authorization;
        volatile boolean stopped;
        public HttpsProxyTunnel(String key, HttpTestServer server, DigestEchoServer target,
                                HttpTestHandler delegate)
                throws IOException {
            this(key, server, target, delegate, ServerSocketFactory.create());
        }
        private HttpsProxyTunnel(String key, HttpTestServer server, DigestEchoServer target,
                                HttpTestHandler delegate, ServerSocket ss)
                throws IOException {
            super("HttpsProxyTunnel:" + ss.getLocalPort() + ":" + key,
                    server, target, delegate);
            System.out.flush();
            System.err.println("WARNING: HttpsProxyTunnel is an experimental test class");
            this.ss = ss;
            start();
        }

        final void start() throws IOException {
            Thread t = new Thread(this"ProxyThread");
            t.setDaemon(true);
            t.start();
        }

        @Override
        public Version getServerVersion() {
            // serverImpl is not null when this proxy
            // serves a single server. It will be null
            // if this proxy can serve multiple servers.
            if (serverImpl != nullreturn serverImpl.getVersion();
            return null;
        }

        @Override
        public void stop() {
            stopped = true;
            if (serverImpl != null) {
                serverImpl.stop();
            }
            if (redirect != null) {
                redirect.stop();
            }
            try {
                ss.close();
            } catch (IOException ex) {
                if (DEBUG) ex.printStackTrace(System.out);
            }
        }


        @Override
        void configureAuthentication(HttpTestContext ctxt,
                                     HttpAuthSchemeType schemeType,
                                     HttpTestAuthenticator auth,
                                     HttpAuthType authType) {
            if (authType == HttpAuthType.PROXY || authType == HttpAuthType.PROXY305) {
                authorization = new ProxyAuthorization(key, schemeType, auth);
            } else {
                super.configureAuthentication(ctxt, schemeType, auth, authType);
            }
        }

        boolean badRequest(StringBuilder response, String hostport, List<String> hosts) {
            String message = null;
            if (hosts.isEmpty()) {
                message = "No host header provided\r\n";
            } else if (hosts.size() > 1) {
                message = "Multiple host headers provided\r\n";
                for (String h : hosts) {
                    message = message + "host: " + h + "\r\n";
                }
            } else {
                String h = hosts.get(0);
                if (!hostport.equalsIgnoreCase(h)
                        && !hostport.equalsIgnoreCase(h + ":80")
                        && !hostport.equalsIgnoreCase(h + ":443")) {
                    message = "Bad host provided: [" + h
                            + "] doesnot match [" + hostport + "]\r\n";
                }
            }
            if (message != null) {
                int length = message.getBytes(StandardCharsets.UTF_8).length;
                response.append("HTTP/1.1 400 BadRequest\r\n")
                        .append("Content-Length: " + length)
                        .append("\r\n\r\n")
                        .append(message);
                return true;
            }

            return false;
        }

        boolean authorize(StringBuilder response, String requestLine, String headers) {
            if (authorization != null) {
                return authorization.authorize(response, requestLine, headers);
            }
            response.append("HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n");
            return true;
        }

        // Pipe the input stream to the output stream.
        private synchronized Thread pipe(InputStream is, OutputStream os, char tag, CompletableFuture<Void> end) {
            return new Thread("TunnelPipe("+tag+")") {
                @Override
                public void run() {
                    try {
                        int c = 0;
                        try {
                            while ((c = is.read()) != -1) {
                                os.write(c);
                                os.flush();
                                // if DEBUG prints a + or a - for each transferred
                                // character.
                                if (DEBUG) System.out.print(tag);
                            }
                            is.close();
                        } catch (IOException ex) {
                            if (DEBUG || !stopped && c >  -1)
                                ex.printStackTrace(System.out);
                            end.completeExceptionally(ex);
                        } finally {
                            try {os.close();} catch (Throwable t) {}
                        }
                    } finally {
                        end.complete(null);
                    }
                }
            };
        }

        @Override
        public InetSocketAddress getAddress() {
            return new InetSocketAddress(InetAddress.getLoopbackAddress(),
                    ss.getLocalPort());
        }
        @Override
        public InetSocketAddress getProxyAddress() {
            return getAddress();
        }
        @Override
        public InetSocketAddress getServerAddress() {
            // serverImpl can be null if this proxy can serve
            // multiple servers.
            if (serverImpl != null) {
                return serverImpl.getAddress();
            }
            return null;
        }


        // This is a bit shaky. It doesn't handle continuation
        // lines, but our client shouldn't send any.
        // Read a line from the input stream, swallowing the final
        // \r\n sequence. Stops at the first \n, doesn't complain
        // if it wasn't preceded by '\r'.
        //
--> --------------------

--> maximum size reached

--> --------------------

88%


¤ Dauer der Verarbeitung: 0.35 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung ist noch experimentell.