/**
   * Connects to the remote machine by establishing a tunnel through a HTTP proxy with Basic
   * authentication. It issues a CONNECT request and authenticates with the HTTP proxy with Basic
   * protocol.
   *
   * @param address remote machine to connect to
   * @return a TCP/IP socket connected to the remote machine
   * @throws IOException if an I/O error occurs during handshake (a network problem)
   */
  private Socket authenticateBasic(InetSocketAddress address, ConnectivitySettings cs)
      throws IOException {
    Socket proxy = new Socket(cs.getProxyHost(), cs.getProxyPort());
    BufferedReader r =
        new BufferedReader(
            new InputStreamReader(new InterruptibleInputStream(proxy.getInputStream())));
    DataOutputStream dos = new DataOutputStream(proxy.getOutputStream());

    String username = cs.getProxyUsername() == null ? "" : cs.getProxyUsername();
    String password = cs.getProxyPassword() == null ? "" : String.valueOf(cs.getProxyPassword());
    String credentials = username + ":" + password;
    String basicCookie = Base64Encoder.encode(credentials.getBytes("US-ASCII"));

    dos.writeBytes("CONNECT ");
    dos.writeBytes(address.getHostName() + ":" + address.getPort());
    dos.writeBytes(" HTTP/1.0\r\n");
    dos.writeBytes("Connection: Keep-Alive\r\n");
    dos.writeBytes("Proxy-Authorization: Basic " + basicCookie + "\r\n");
    dos.writeBytes("\r\n");
    dos.flush();

    String line = r.readLine();
    if (sConnectionEstablishedPattern.matcher(line).find()) {
      for (; ; ) {
        line = r.readLine();
        if (line.length() == 0) break;
      }
      return proxy;
    }
    throw new IOException("Basic authentication failed: " + line);
  }
  /**
   * Connects to the remote machine by establishing a tunnel through a HTTP proxy. It issues a
   * CONNECT request and eventually authenticates with the HTTP proxy. Supported authentication
   * methods include: Basic.
   *
   * @param address remote machine to connect to
   * @return a TCP/IP socket connected to the remote machine
   * @throws UnknownHostException if the proxy host name cannot be resolved
   * @throws IOException if an I/O error occurs during handshake (a network problem)
   */
  private Socket getHttpsTunnelSocket(
      InetSocketAddress address, ConnectivitySettings cs, int timeout) throws IOException {
    Socket proxy = new Socket();
    proxy.connect(new InetSocketAddress(cs.getProxyHost(), cs.getProxyPort()), timeout);
    BufferedReader r =
        new BufferedReader(
            new InputStreamReader(new InterruptibleInputStream(proxy.getInputStream())));
    DataOutputStream dos = new DataOutputStream(proxy.getOutputStream());

    dos.writeBytes("CONNECT ");
    dos.writeBytes(address.getHostName() + ":" + address.getPort());
    dos.writeBytes(" HTTP/1.0\r\n");
    dos.writeBytes("Connection: Keep-Alive\r\n\r\n");
    dos.flush();

    String line;
    line = r.readLine();

    if (sConnectionEstablishedPattern.matcher(line).find()) {
      for (; ; ) {
        line = r.readLine();
        if (line.length() == 0) break;
      }
      return proxy;
    } else if (sProxyAuthRequiredPattern.matcher(line).find()) {
      boolean authMethodSelected = false;
      String authMethod = AUTH_NONE;
      for (; ; ) {
        line = r.readLine();
        if (line.length() == 0) break;
        if (line.startsWith("Proxy-Authenticate:") && !authMethodSelected) {
          authMethod = line.substring(19).trim();
          if (authMethod.equals(AUTH_BASIC)) {
            authMethodSelected = true;
          }
        }
      }
      // TODO: need to read full response before closing connection?
      proxy.close();

      if (authMethod.startsWith(AUTH_BASIC)) {
        return authenticateBasic(address, cs);
      } else {
        throw new IOException("Unsupported authentication method: " + authMethod);
      }
    } else {
      proxy.close();
      throw new IOException("HTTP proxy does not support CONNECT command. Received reply: " + line);
    }
  }