/**
   * Opens the connection to a http server or proxy.
   *
   * @return the opened low level connection
   * @throws IOException if the connection fails for any reason.
   */
  @Override
  AndroidHttpClientConnection openConnection(Request req) throws IOException {
    SSLSocket sslSock = null;

    if (mProxyHost != null) {
      // If we have a proxy set, we first send a CONNECT request
      // to the proxy; if the proxy returns 200 OK, we negotiate
      // a secure connection to the target server via the proxy.
      // If the request fails, we drop it, but provide the event
      // handler with the response status and headers. The event
      // handler is then responsible for cancelling the load or
      // issueing a new request.
      AndroidHttpClientConnection proxyConnection = null;
      Socket proxySock = null;
      try {
        proxySock = new Socket(mProxyHost.getHostName(), mProxyHost.getPort());

        proxySock.setSoTimeout(60 * 1000);

        proxyConnection = new AndroidHttpClientConnection();
        HttpParams params = new BasicHttpParams();
        HttpConnectionParams.setSocketBufferSize(params, 8192);

        proxyConnection.bind(proxySock, params);
      } catch (IOException e) {
        if (proxyConnection != null) {
          proxyConnection.close();
        }

        String errorMessage = e.getMessage();
        if (errorMessage == null) {
          errorMessage = "failed to establish a connection to the proxy";
        }

        throw new IOException(errorMessage);
      }

      StatusLine statusLine = null;
      int statusCode = 0;
      Headers headers = new Headers();
      try {
        BasicHttpRequest proxyReq = new BasicHttpRequest("CONNECT", mHost.toHostString());

        // add all 'proxy' headers from the original request, we also need
        // to add 'host' header unless we want proxy to answer us with a
        // 400 Bad Request
        for (Header h : req.mHttpRequest.getAllHeaders()) {
          String headerName = h.getName().toLowerCase(Locale.ROOT);
          if (headerName.startsWith("proxy")
              || headerName.equals("keep-alive")
              || headerName.equals("host")) {
            proxyReq.addHeader(h);
          }
        }

        proxyConnection.sendRequestHeader(proxyReq);
        proxyConnection.flush();

        // it is possible to receive informational status
        // codes prior to receiving actual headers;
        // all those status codes are smaller than OK 200
        // a loop is a standard way of dealing with them
        do {
          statusLine = proxyConnection.parseResponseHeader(headers);
          statusCode = statusLine.getStatusCode();
        } while (statusCode < HttpStatus.SC_OK);
      } catch (ParseException e) {
        String errorMessage = e.getMessage();
        if (errorMessage == null) {
          errorMessage = "failed to send a CONNECT request";
        }

        throw new IOException(errorMessage);
      } catch (HttpException e) {
        String errorMessage = e.getMessage();
        if (errorMessage == null) {
          errorMessage = "failed to send a CONNECT request";
        }

        throw new IOException(errorMessage);
      } catch (IOException e) {
        String errorMessage = e.getMessage();
        if (errorMessage == null) {
          errorMessage = "failed to send a CONNECT request";
        }

        throw new IOException(errorMessage);
      }

      if (statusCode == HttpStatus.SC_OK) {
        try {
          sslSock =
              (SSLSocket)
                  getSocketFactory()
                      .createSocket(proxySock, mHost.getHostName(), mHost.getPort(), true);
        } catch (IOException e) {
          if (sslSock != null) {
            sslSock.close();
          }

          String errorMessage = e.getMessage();
          if (errorMessage == null) {
            errorMessage = "failed to create an SSL socket";
          }
          throw new IOException(errorMessage);
        }
      } else {
        // if the code is not OK, inform the event handler
        ProtocolVersion version = statusLine.getProtocolVersion();

        req.mEventHandler.status(
            version.getMajor(), version.getMinor(), statusCode, statusLine.getReasonPhrase());
        req.mEventHandler.headers(headers);
        req.mEventHandler.endData();

        proxyConnection.close();

        // here, we return null to indicate that the original
        // request needs to be dropped
        return null;
      }
    } else {
      // if we do not have a proxy, we simply connect to the host
      try {
        sslSock = (SSLSocket) getSocketFactory().createSocket(mHost.getHostName(), mHost.getPort());
        sslSock.setSoTimeout(SOCKET_TIMEOUT);
      } catch (IOException e) {
        if (sslSock != null) {
          sslSock.close();
        }

        String errorMessage = e.getMessage();
        if (errorMessage == null) {
          errorMessage = "failed to create an SSL socket";
        }

        throw new IOException(errorMessage);
      }
    }

    // do handshake and validate server certificates
    SslError error =
        CertificateChainValidator.getInstance()
            .doHandshakeAndValidateServerCertificates(this, sslSock, mHost.getHostName());

    // Inform the user if there is a problem
    if (error != null) {
      // handleSslErrorRequest may immediately unsuspend if it wants to
      // allow the certificate anyway.
      // So we mark the connection as suspended, call handleSslErrorRequest
      // then check if we're still suspended and only wait if we actually
      // need to.
      synchronized (mSuspendLock) {
        mSuspended = true;
      }
      // don't hold the lock while calling out to the event handler
      boolean canHandle = req.getEventHandler().handleSslErrorRequest(error);
      if (!canHandle) {
        throw new IOException("failed to handle " + error);
      }
      synchronized (mSuspendLock) {
        if (mSuspended) {
          try {
            // Put a limit on how long we are waiting; if the timeout
            // expires (which should never happen unless you choose
            // to ignore the SSL error dialog for a very long time),
            // we wake up the thread and abort the request. This is
            // to prevent us from stalling the network if things go
            // very bad.
            mSuspendLock.wait(10 * 60 * 1000);
            if (mSuspended) {
              // mSuspended is true if we have not had a chance to
              // restart the connection yet (ie, the wait timeout
              // has expired)
              mSuspended = false;
              mAborted = true;
              if (HttpLog.LOGV) {
                HttpLog.v(
                    "HttpsConnection.openConnection():"
                        + " SSL timeout expired and request was cancelled!!!");
              }
            }
          } catch (InterruptedException e) {
            // ignore
          }
        }
        if (mAborted) {
          // The user decided not to use this unverified connection
          // so close it immediately.
          sslSock.close();
          throw new SSLConnectionClosedByUserException("connection closed by the user");
        }
      }
    }

    // All went well, we have an open, verified connection.
    AndroidHttpClientConnection conn = new AndroidHttpClientConnection();
    BasicHttpParams params = new BasicHttpParams();
    params.setIntParameter(HttpConnectionParams.SOCKET_BUFFER_SIZE, 8192);
    conn.bind(sslSock, params);

    return conn;
  }
 /* Assert.asserts that the components of two status lines match. */
 public static boolean equivalent(StatusLine l1, StatusLine l2) {
   return (l1.getProtocolVersion().equals(l2.getProtocolVersion())
       && semanticallyTransparent(l1, l2));
 }