/**
     * If we're establishing an HTTPS tunnel with CONNECT (RFC 2817 5.2), send only the minimum set
     * of headers. This avoids sending potentially sensitive data like HTTP cookies to the proxy
     * unencrypted.
     */
    @Override
    protected RawHeaders getNetworkRequestHeaders() throws IOException {
      RequestHeaders privateHeaders = getRequestHeaders();
      URL url = policy.getURL();

      RawHeaders result = new RawHeaders();
      result.setStatusLine(
          "CONNECT " + url.getHost() + ":" + URLs.getEffectivePort(url) + " HTTP/1.1");

      // Always set Host and User-Agent.
      String host = privateHeaders.getHost();
      if (host == null) {
        host = getOriginAddress(url);
      }
      result.set("Host", host);

      String userAgent = privateHeaders.getUserAgent();
      if (userAgent == null) {
        userAgent = getDefaultUserAgent();
      }
      result.set("User-Agent", userAgent);

      // Copy over the Proxy-Authorization header if it exists.
      String proxyAuthorization = privateHeaders.getProxyAuthorization();
      if (proxyAuthorization != null) {
        result.set("Proxy-Authorization", proxyAuthorization);
      }

      // Always set the Proxy-Connection to Keep-Alive for the benefit of
      // HTTP/1.0 proxies like Squid.
      result.set("Proxy-Connection", "Keep-Alive");
      return result;
    }
    /**
     * To make an HTTPS connection over an HTTP proxy, send an unencrypted CONNECT request to create
     * the proxy connection. This may need to be retried if the proxy requires authorization.
     */
    private void makeTunnel(
        HttpURLConnectionImpl policy, HttpConnection connection, RequestHeaders requestHeaders)
        throws IOException {
      RawHeaders rawRequestHeaders = requestHeaders.getHeaders();
      while (true) {
        HttpEngine connect = new ProxyConnectEngine(policy, rawRequestHeaders, connection);
        connect.sendRequest();
        connect.readResponse();

        int responseCode = connect.getResponseCode();
        switch (connect.getResponseCode()) {
          case HTTP_OK:
            return;
          case HTTP_PROXY_AUTH:
            rawRequestHeaders = new RawHeaders(rawRequestHeaders);
            boolean credentialsFound =
                policy.processAuthHeader(
                    HTTP_PROXY_AUTH, connect.getResponseHeaders(), rawRequestHeaders);
            if (credentialsFound) {
              continue;
            } else {
              throw new IOException("Failed to authenticate with proxy");
            }
          default:
            throw new IOException("Unexpected response code for CONNECT: " + responseCode);
        }
      }
    }
  /** Returns true if this response can be stored to later serve another request. */
  public boolean isCacheable(RequestHeaders request) {
    /*
     * Always go to network for uncacheable response codes (RFC 2616, 13.4),
     * This implementation doesn't support caching partial content.
     */
    int responseCode = headers.getResponseCode();
    if (responseCode != HttpURLConnection.HTTP_OK
        && responseCode != HttpURLConnection.HTTP_NOT_AUTHORITATIVE
        && responseCode != HttpURLConnection.HTTP_MULT_CHOICE
        && responseCode != HttpURLConnection.HTTP_MOVED_PERM
        && responseCode != HttpURLConnection.HTTP_GONE) {
      return false;
    }

    /*
     * Responses to authorized requests aren't cacheable unless they include
     * a 'public', 'must-revalidate' or 's-maxage' directive.
     */
    if (request.hasAuthorization() && !isPublic && !mustRevalidate && sMaxAgeSeconds == -1) {
      return false;
    }

    if (noStore) {
      return false;
    }

    return true;
  }
  /** Returns the source to satisfy {@code request} given this cached response. */
  public ResponseSource chooseResponseSource(long nowMillis, RequestHeaders request) {
    /*
     * If this response shouldn't have been stored, it should never be used
     * as a response source. This check should be redundant as long as the
     * persistence store is well-behaved and the rules are constant.
     */
    if (!isCacheable(request)) {
      return ResponseSource.NETWORK;
    }

    if (request.isNoCache() || request.hasConditions()) {
      return ResponseSource.NETWORK;
    }

    long ageMillis = computeAge(nowMillis);
    long freshMillis = computeFreshnessLifetime();

    if (request.getMaxAgeSeconds() != -1) {
      freshMillis = Math.min(freshMillis, TimeUnit.SECONDS.toMillis(request.getMaxAgeSeconds()));
    }

    long minFreshMillis = 0;
    if (request.getMinFreshSeconds() != -1) {
      minFreshMillis = TimeUnit.SECONDS.toMillis(request.getMinFreshSeconds());
    }

    long maxStaleMillis = 0;
    if (!mustRevalidate && request.getMaxStaleSeconds() != -1) {
      maxStaleMillis = TimeUnit.SECONDS.toMillis(request.getMaxStaleSeconds());
    }

    if (!noCache && ageMillis + minFreshMillis < freshMillis + maxStaleMillis) {
      if (ageMillis + minFreshMillis >= freshMillis) {
        headers.add("Warning", "110 HttpURLConnection \"Response is stale\"");
      }
      if (ageMillis > TimeUnit.HOURS.toMillis(24) && isFreshnessLifetimeHeuristic()) {
        headers.add("Warning", "113 HttpURLConnection \"Heuristic expiration\"");
      }
      return ResponseSource.CACHE;
    }

    if (lastModified != null) {
      request.setIfModifiedSince(lastModified);
    } else if (servedDate != null) {
      request.setIfModifiedSince(servedDate);
    }

    if (etag != null) {
      request.setIfNoneMatch(etag);
    }

    return request.hasConditions() ? ResponseSource.CONDITIONAL_CACHE : ResponseSource.NETWORK;
  }