/** * 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; }