package com.koushikdutta.async.http;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FilterInputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.math.BigInteger;
import java.net.CacheRequest;
import java.net.CacheResponse;
import java.net.SecureCacheResponse;
import java.net.URI;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Principal;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import javax.net.ssl.SSLPeerUnverifiedException;
import android.os.Parcel;
import android.os.Parcelable;
import com.hupu.games.common.Base64;
import com.koushikdutta.async.AsyncSSLSocket;
import com.koushikdutta.async.AsyncServer;
import com.koushikdutta.async.AsyncSocket;
import com.koushikdutta.async.ByteBufferList;
import com.koushikdutta.async.DataEmitter;
import com.koushikdutta.async.DataEmitterBase;
import com.koushikdutta.async.FilteredDataEmitter;
import com.koushikdutta.async.Util;
import com.koushikdutta.async.callback.CompletedCallback;
import com.koushikdutta.async.callback.WritableCallback;
import com.koushikdutta.async.future.Cancellable;
import com.koushikdutta.async.future.SimpleCancellable;
import com.koushikdutta.async.http.libcore.Charsets;
import com.koushikdutta.async.http.libcore.DiskLruCache;
import com.koushikdutta.async.http.libcore.RawHeaders;
import com.koushikdutta.async.http.libcore.ResponseHeaders;
import com.koushikdutta.async.http.libcore.ResponseSource;
import com.koushikdutta.async.http.libcore.StrictLineReader;
public class ResponseCacheMiddleware extends SimpleMiddleware {
private DiskLruCache cache;
private static final int VERSION = 201105;
private static final int ENTRY_METADATA = 0;
private static final int ENTRY_BODY = 1;
private static final int ENTRY_COUNT = 2;
private AsyncHttpClient client;
public static final String SERVED_FROM = "X-Served-From";
public static final String CONDITIONAL_CACHE = "conditional-cache";
public static final String CACHE = "cache";
private ResponseCacheMiddleware() {
}
long size;
File cacheDir;
public static ResponseCacheMiddleware addCache(AsyncHttpClient client, File cacheDir, long size) throws IOException {
for (AsyncHttpClientMiddleware middleware: client.getMiddleware()) {
if (middleware instanceof ResponseCacheMiddleware)
throw new IOException("Response cache already added to http client");
}
ResponseCacheMiddleware ret = new ResponseCacheMiddleware();
ret.size = size;
ret.client = client;
ret.cacheDir = cacheDir;
ret.open();
client.insertMiddleware(ret);
return ret;
}
private void open() throws IOException {
cache = DiskLruCache.open(cacheDir, VERSION, ENTRY_COUNT, size);
}
boolean caching = true;
public void setCaching(boolean caching) {
this.caching = caching;
}
public boolean getCaching() {
return caching;
}
private static String uriToKey(URI uri) {
try {
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
byte[] md5bytes = messageDigest.digest(uri.toString().getBytes());
return new BigInteger(1, md5bytes).toString(16);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
private class CachedSSLSocket extends CachedSocket implements AsyncSSLSocket {
public CachedSSLSocket(CacheResponse cacheResponse, long contentLength) {
super(cacheResponse, contentLength);
}
@Override
public X509Certificate[] getPeerCertificates() {
return null;
}
}
private class CachedSocket extends DataEmitterBase implements AsyncSocket {
CacheResponse cacheResponse;
long contentLength;
public CachedSocket(CacheResponse cacheResponse, long contentLength) {
this.cacheResponse = cacheResponse;
this.contentLength = contentLength;
}
@Override
public void end() {
}
@Override
public boolean isChunked() {
return false;
}
boolean paused;
@Override
public void pause() {
paused = true;
}
boolean closed;
@Override
protected void report(Exception e) {
super.report(e);
try {
cacheResponse.getBody().close();
}
catch (Exception ex) {
}
if (closed)
return;
closed = true;
if (closedCallback != null)
closedCallback.onCompleted(e);
}
boolean first = true;
void spewInternal() {
if (pending.remaining() > 0) {
com.koushikdutta.async.Util.emitAllData(CachedSocket.this, pending);
if (pending.remaining() > 0)
return;
}
// fill pending
try {
assert first;
if (!first)
return;
first = false;
ByteBuffer buffer = ByteBufferList.obtain((int)contentLength);
assert buffer.position() == 0;
DataInputStream din = new DataInputStream(cacheResponse.getBody());
din.readFully(buffer.array(), buffer.arrayOffset(), (int)contentLength);
pending.add(buffer);
com.koushikdutta.async.Util.emitAllData(CachedSocket.this, pending);
assert din.read() == -1;
report(null);
}
catch (IOException e) {
report(e);
}
}
ByteBufferList pending = new ByteBufferList();
void spew() {
getServer().post(new Runnable() {
@Override
public void run() {
spewInternal();
}
});
}
@Override
public void resume() {
paused = false;
spew();
}
@Override
public boolean isPaused() {
return paused;
}
@Override
public void write(ByteBuffer bb) {
// it's gonna write headers and stuff... whatever
bb.limit(bb.position());
}
@Override
public void write(ByteBufferList bb) {
// it's gonna write headers and stuff... whatever
bb.recycle();
}
@Override
public void setWriteableCallback(WritableCallback handler) {
}
@Override
public WritableCallback getWriteableCallback() {
return null;
}
boolean open;
@Override
public boolean isOpen() {
return open;
}
@Override
public void close() {
open = false;
}
@Override
public void setClosedCallback(CompletedCallback handler) {
closedCallback = handler;
}
CompletedCallback closedCallback;
@Override
public CompletedCallback getClosedCallback() {
return closedCallback;
}
@Override
public AsyncServer getServer() {
return client.getServer();
}
}
public static class CacheData implements Parcelable {
DiskLruCache.Snapshot snapshot;
CacheResponse candidate;
long contentLength;
ResponseHeaders cachedResponseHeaders;
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
}
}
private static final String LOGTAG = "AsyncHttpCache";
// step 1) see if we can serve request from the cache directly.
// also see if this can be turned into a conditional cache request.
@Override
public Cancellable getSocket(final GetSocketData data) {
if (cache == null || !caching || data.request.getHeaders().isNoCache()) {
networkCount++;
return null;
}
String key = uriToKey(data.request.getUri());
DiskLruCache.Snapshot snapshot = null;
Entry entry;
try {
snapshot = cache.get(key);
if (snapshot == null) {
networkCount++;
return null;
}
entry = new Entry(snapshot.getInputStream(ENTRY_METADATA));
} catch (IOException e) {
// Give up because the cache cannot be read.
networkCount++;
return null;
}
// verify the entry matches
if (!entry.matches(data.request.getUri(), data.request.getMethod(), data.request.getHeaders().getHeaders().toMultimap())) {
networkCount++;
snapshot.close();
return null;
}
CacheResponse candidate = entry.isHttps() ? new EntrySecureCacheResponse(entry, snapshot) : new EntryCacheResponse(entry, snapshot);
Map<String, List<String>> responseHeadersMap;
InputStream cachedResponseBody;
try {
responseHeadersMap = candidate.getHeaders();
cachedResponseBody = candidate.getBody();
}
catch (Exception e) {
networkCount++;
snapshot.close();
return null;
}
if (responseHeadersMap == null || cachedResponseBody == null) {
try {
cachedResponseBody.close();
}
catch (Exception e) {
}
networkCount++;
snapshot.close();
return null;
}
RawHeaders rawResponseHeaders = RawHeaders.fromMultimap(responseHeadersMap);
ResponseHeaders cachedResponseHeaders = new ResponseHeaders(data.request.getUri(), rawResponseHeaders);
cachedResponseHeaders.setLocalTimestamps(System.currentTimeMillis(), System.currentTimeMillis());
long now = System.currentTimeMillis();
ResponseSource responseSource = cachedResponseHeaders.chooseResponseSource(now, data.request.getHeaders());
long contentLength = snapshot.getLength(ENTRY_BODY);
if (responseSource == ResponseSource.CACHE) {
data.request.logi("Response retrieved from cache");
final CachedSocket socket = entry.isHttps() ? new CachedSSLSocket((EntrySecureCacheResponse)candidate, contentLength) : new CachedSocket((EntryCacheResponse)candidate, contentLength);
rawResponseHeaders.removeAll("Content-Encoding");
rawResponseHeaders.removeAll("Transfer-Encoding");
rawResponseHeaders.set("Content-Length", String.valueOf(contentLength));
socket.pending.add(ByteBuffer.wrap(rawResponseHeaders.toHeaderString().getBytes()));
client.getServer().post(new Runnable() {
@Override
public void run() {
data.connectCallback.onConnectCompleted(null, socket);
socket.spewInternal();
}
});
cacheHitCount++;
return new SimpleCancellable();
}
else if (responseSource == ResponseSource.CONDITIONAL_CACHE) {
data.request.logi("Response may be served from conditional cache");
CacheData cacheData = new CacheData();
cacheData.snapshot = snapshot;
cacheData.contentLength = contentLength;
cacheData.cachedResponseHeaders = cachedResponseHeaders;
cacheData.candidate = candidate;
data.state.putParcelable("cache-data", cacheData);
return null;
}
else {
data.request.logd("Response can not be served from cache");
// NETWORK or other
try {
cachedResponseBody.close();
}
catch (Exception e) {
}
networkCount++;
snapshot.close();
return null;
}
}
private static class BodyCacher extends FilteredDataEmitter implements Parcelable {
CacheRequestImpl cacheRequest;
ByteBufferList cached;
@Override
protected void report(Exception e) {
super.report(e);
if (e != null)
abort();
}
@Override
public void onDataAvailable(DataEmitter emitter, ByteBufferList bb) {
if (cached != null) {
com.koushikdutta.async.Util.emitAllData(this, cached);
// couldn't emit it all, so just wait for another day...
if (cached.remaining() > 0)
return;
cached = null;
}
// write to cache... any data not consumed needs to be retained for the next callback
try {
if (cacheRequest != null) {
OutputStream outputStream = cacheRequest.getBody();
if (outputStream != null) {
int count = bb.size();
for (int i = 0; i < count; i++) {
ByteBuffer b = bb.remove();
outputStream.write(b.array(), b.arrayOffset() + b.position(), b.remaining());
bb.add(b);
}
}
else {
abort();
}
}
}
catch (Exception e) {
abort();
}
super.onDataAvailable(emitter, bb);
if (cacheRequest != null && bb.remaining() > 0) {
cached = new ByteBufferList();
bb.get(cached);
}
}
public void abort() {
if (cacheRequest != null) {
cacheRequest.abort();
cacheRequest = null;
}
}
public void commit() {
if (cacheRequest != null) {
try {
cacheRequest.getBody().close();
}
catch (Exception e) {
}
}
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
}
}
private static class BodySpewer extends FilteredDataEmitter {
long contentLength;
public BodySpewer(long contentLength) {
this.contentLength = contentLength;
}
CacheResponse cacheResponse;
boolean first = true;
void spewInternal() {
if (pending.remaining() > 0) {
com.koushikdutta.async.Util.emitAllData(BodySpewer.this, pending);
if (pending.remaining() > 0)
return;
}
// fill pending
try {
assert first;
if (!first)
return;
first = false;
ByteBuffer buffer = ByteBufferList.obtain((int)contentLength);
assert buffer.position() == 0;
DataInputStream din = new DataInputStream(cacheResponse.getBody());
din.readFully(buffer.array(), buffer.arrayOffset(), (int)contentLength);
pending.add(buffer);
com.koushikdutta.async.Util.emitAllData(this, pending);
assert din.read() == -1;
allowEnd = true;
report(null);
}
catch (IOException e) {
allowEnd = true;
report(e);
}
}
ByteBufferList pending = new ByteBufferList();
void spew() {
getServer().post(new Runnable() {
@Override
public void run() {
spewInternal();
}
});
}
boolean paused;
@Override
public void resume() {
paused = false;
spew();
}
@Override
public boolean isPaused() {
return paused;
}
boolean allowEnd;
@Override
protected void report(Exception e) {
if (!allowEnd)
return;
try {
cacheResponse.getBody().close();
}
catch (Exception ex) {
}
super.report(e);
}
}
private int conditionalCacheHitCount;
private int cacheHitCount;
private int networkCount;
private int cacheStoreCount;
public int getConditionalCacheHitCount() {
return conditionalCacheHitCount;
}
public int getCacheHitCount() {
return cacheHitCount;
}
public int getNetworkCount() {
return networkCount;
}
public int getCacheStoreCount() {
return cacheStoreCount;
}
// step 3) if this is a conditional cache request, serve it from the cache if necessary
// otherwise, see if it is cacheable
@Override
public void onBodyDecoder(OnBodyData data) {
CachedSocket cached = (CachedSocket) com.koushikdutta.async.Util.getWrappedSocket(data.socket, CachedSocket.class);
if (cached != null) {
data.headers.getHeaders().set(SERVED_FROM, CACHE);
return;
}
CacheData cacheData = data.state.getParcelable("cache-data");
if (cacheData != null) {
if (cacheData.cachedResponseHeaders.validate(data.headers)) {
data.request.logi("Serving response from conditional cache");
data.headers = cacheData.cachedResponseHeaders.combine(data.headers);
data.headers.getHeaders().setStatusLine(cacheData.cachedResponseHeaders.getHeaders().getStatusLine());
data.headers.getHeaders().set(SERVED_FROM, CONDITIONAL_CACHE);
conditionalCacheHitCount++;
BodySpewer bodySpewer = new BodySpewer(cacheData.contentLength);
bodySpewer.cacheResponse = cacheData.candidate;
bodySpewer.setDataEmitter(data.bodyEmitter);
data.bodyEmitter = bodySpewer;
bodySpewer.spew();
return;
}
// did not validate, so fall through and cache the response
data.state.remove("cache-data");
cacheData.snapshot.close();
}
if (!caching)
return;
if (!data.headers.isCacheable(data.request.getHeaders()) || !data.request.getMethod().equals(AsyncHttpGet.METHOD)) {
/*
* Don't cache non-GET responses. We're technically allowed to cache
* HEAD requests and some POST requests, but the complexity of doing
* so is high and the benefit is low.
*/
networkCount++;
data.request.logd("Response is not cacheable");
return;
}
String key = uriToKey(data.request.getUri());
RawHeaders varyHeaders = data.request.getHeaders().getHeaders().getAll(data.headers.getVaryFields());
Entry entry = new Entry(data.request.getUri(), varyHeaders, data.request, data.headers);
DiskLruCache.Editor editor = null;
BodyCacher cacher = new BodyCacher();
try {
editor = cache.edit(key);
if (editor == null) {
// Log.i(LOGTAG, "can't cache");
return;
}
entry.writeTo(editor);
cacher.cacheRequest = new CacheRequestImpl(editor);
if (cacher.cacheRequest.getBody() == null)
return;
// cacher.cacheData =
cacher.setDataEmitter(data.bodyEmitter);
data.bodyEmitter = cacher;
data.state.putParcelable("body-cacher", cacher);
data.request.logd("Caching response");
cacheStoreCount++;
}
catch (Exception e) {
// Log.e(LOGTAG, "error", e);
if (cacher.cacheRequest != null)
cacher.cacheRequest.abort();
cacher.cacheRequest = null;
networkCount++;
}
}
@Override
public void onRequestComplete(OnRequestCompleteData data) {
CacheData cacheData = data.state.getParcelable("cache-data");
if (cacheData != null && cacheData.snapshot != null)
cacheData.snapshot.close();
CachedSocket cachedSocket = Util.getWrappedSocket(data.socket, CachedSocket.class);
if (cachedSocket != null)
((SnapshotCacheResponse)cachedSocket.cacheResponse).getSnapshot().close();
BodyCacher cacher = data.state.getParcelable("body-cacher");
if (cacher != null) {
try {
if (data.exception != null)
cacher.abort();
else
cacher.commit();
}
catch (Exception e) {
}
}
}
int writeSuccessCount;
int writeAbortCount;
private final class CacheRequestImpl extends CacheRequest {
private final DiskLruCache.Editor editor;
private OutputStream cacheOut;
private boolean done;
private OutputStream body;
public CacheRequestImpl(final DiskLruCache.Editor editor) throws IOException {
this.editor = editor;
this.cacheOut = editor.newOutputStream(ENTRY_BODY);
this.body = new FilterOutputStream(cacheOut) {
@Override public void close() throws IOException {
synchronized (ResponseCacheMiddleware.this) {
if (done) {
return;
}
done = true;
writeSuccessCount++;
}
super.close();
editor.commit();
}
@Override
public void write(byte[] buffer, int offset, int length) throws IOException {
// Since we don't override "write(int oneByte)", we can write directly to "out"
// and avoid the inefficient implementation from the FilterOutputStream.
out.write(buffer, offset, length);
}
};
}
@Override public void abort() {
synchronized (ResponseCacheMiddleware.this) {
if (done) {
return;
}
done = true;
writeAbortCount++;
}
try {
cacheOut.close();
}
catch (IOException e) {
}
try {
editor.abort();
} catch (IOException ignored) {
}
}
@Override public OutputStream getBody() throws IOException {
return body;
}
}
private static final class Entry {
private final String uri;
private final RawHeaders varyHeaders;
private final String requestMethod;
private final RawHeaders responseHeaders;
private final String cipherSuite;
private final Certificate[] peerCertificates;
private final Certificate[] localCertificates;
/*
* Reads an entry from an input stream. A typical entry looks like this:
* http://google.com/foo
* GET
* 2
* Accept-Language: fr-CA
* Accept-Charset: UTF-8
* HTTP/1.1 200 OK
* 3
* Content-Type: image/png
* Content-Length: 100
* Cache-Control: max-age=600
*
* A typical HTTPS file looks like this:
* https://google.com/foo
* GET
* 2
* Accept-Language: fr-CA
* Accept-Charset: UTF-8
* HTTP/1.1 200 OK
* 3
* Content-Type: image/png
* Content-Length: 100
* Cache-Control: max-age=600
*
* AES_256_WITH_MD5
* 2
* base64-encoded peerCertificate[0]
* base64-encoded peerCertificate[1]
* -1
*
* The file is newline separated. The first two lines are the URL and
* the request method. Next is the number of HTTP Vary request header
* lines, followed by those lines.
*
* Next is the response status line, followed by the number of HTTP
* response header lines, followed by those lines.
*
* HTTPS responses also contain SSL session information. This begins
* with a blank line, and then a line containing the cipher suite. Next
* is the length of the peer certificate chain. These certificates are
* base64-encoded and appear each on their own line. The next line
* contains the length of the local certificate chain. These
* certificates are also base64-encoded and appear each on their own
* line. A length of -1 is used to encode a null array.
*/
public Entry(InputStream in) throws IOException {
try {
StrictLineReader reader = new StrictLineReader(in, Charsets.US_ASCII);
uri = reader.readLine();
requestMethod = reader.readLine();
varyHeaders = new RawHeaders();
int varyRequestHeaderLineCount = reader.readInt();
for (int i = 0; i < varyRequestHeaderLineCount; i++) {
varyHeaders.addLine(reader.readLine());
}
responseHeaders = new RawHeaders();
responseHeaders.setStatusLine(reader.readLine());
int responseHeaderLineCount = reader.readInt();
for (int i = 0; i < responseHeaderLineCount; i++) {
responseHeaders.addLine(reader.readLine());
}
// if (isHttps()) {
// String blank = reader.readLine();
// if (blank.length() != 0) {
// throw new IOException("expected \"\" but was \"" + blank + "\"");
// }
// cipherSuite = reader.readLine();
// peerCertificates = readCertArray(reader);
// localCertificates = readCertArray(reader);
// } else {
cipherSuite = null;
peerCertificates = null;
localCertificates = null;
// }
} finally {
in.close();
}
}
public Entry(URI uri, RawHeaders varyHeaders, AsyncHttpRequest request, ResponseHeaders responseHeaders) {
this.uri = uri.toString();
this.varyHeaders = varyHeaders;
this.requestMethod = request.getMethod();
this.responseHeaders = responseHeaders.getHeaders();
// if (isHttps()) {
// HttpsURLConnection httpsConnection = (HttpsURLConnection) httpConnection;
// cipherSuite = httpsConnection.getCipherSuite();
// Certificate[] peerCertificatesNonFinal = null;
// try {
// peerCertificatesNonFinal = httpsConnection.getServerCertificates();
// } catch (SSLPeerUnverifiedException ignored) {
// }
// peerCertificates = peerCertificatesNonFinal;
// localCertificates = httpsConnection.getLocalCertificates();
// } else {
cipherSuite = null;
peerCertificates = null;
localCertificates = null;
// }
}
public void writeTo(DiskLruCache.Editor editor) throws IOException {
OutputStream out = editor.newOutputStream(ENTRY_METADATA);
Writer writer = new BufferedWriter(new OutputStreamWriter(out, Charsets.UTF_8));
writer.write(uri + '\n');
writer.write(requestMethod + '\n');
writer.write(Integer.toString(varyHeaders.length()) + '\n');
for (int i = 0; i < varyHeaders.length(); i++) {
writer.write(varyHeaders.getFieldName(i) + ": "
+ varyHeaders.getValue(i) + '\n');
}
writer.write(responseHeaders.getStatusLine() + '\n');
writer.write(Integer.toString(responseHeaders.length()) + '\n');
for (int i = 0; i < responseHeaders.length(); i++) {
writer.write(responseHeaders.getFieldName(i) + ": "
+ responseHeaders.getValue(i) + '\n');
}
if (isHttps()) {
writer.write('\n');
writer.write(cipherSuite + '\n');
writeCertArray(writer, peerCertificates);
writeCertArray(writer, localCertificates);
}
writer.close();
}
private boolean isHttps() {
return uri.startsWith("https://");
}
private Certificate[] readCertArray(StrictLineReader reader) throws IOException {
int length = reader.readInt();
if (length == -1) {
return null;
}
try {
CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
Certificate[] result = new Certificate[length];
for (int i = 0; i < result.length; i++) {
String line = reader.readLine();
byte[] bytes = Base64.decode(line, Base64.DEFAULT);
result[i] = certificateFactory.generateCertificate(
new ByteArrayInputStream(bytes));
}
return result;
} catch (CertificateException e) {
throw new IOException(e.getMessage());
}
}
private void writeCertArray(Writer writer, Certificate[] certificates) throws IOException {
if (certificates == null) {
writer.write("-1\n");
return;
}
try {
writer.write(Integer.toString(certificates.length) + '\n');
for (Certificate certificate : certificates) {
byte[] bytes = certificate.getEncoded();
String line = Base64.encodeToString(bytes, Base64.DEFAULT);
writer.write(line + '\n');
}
} catch (CertificateEncodingException e) {
throw new IOException(e.getMessage());
}
}
public boolean matches(URI uri, String requestMethod,
Map<String, List<String>> requestHeaders) {
return this.uri.equals(uri.toString())
&& this.requestMethod.equals(requestMethod)
&& new ResponseHeaders(uri, responseHeaders)
.varyMatches(varyHeaders.toMultimap(), requestHeaders);
}
}
/**
* Returns an input stream that reads the body of a snapshot, closing the
* snapshot when the stream is closed.
*/
private static InputStream newBodyInputStream(final DiskLruCache.Snapshot snapshot) {
return new FilterInputStream(snapshot.getInputStream(ENTRY_BODY)) {
@Override public void close() throws IOException {
snapshot.close();
super.close();
}
};
}
static interface SnapshotCacheResponse {
public DiskLruCache.Snapshot getSnapshot();
}
static class EntryCacheResponse extends CacheResponse implements SnapshotCacheResponse {
private final Entry entry;
private final DiskLruCache.Snapshot snapshot;
private final InputStream in;
@Override
public DiskLruCache.Snapshot getSnapshot() {
return snapshot;
}
public EntryCacheResponse(Entry entry, DiskLruCache.Snapshot snapshot) {
this.entry = entry;
this.snapshot = snapshot;
this.in = newBodyInputStream(snapshot);
}
@Override public Map<String, List<String>> getHeaders() {
return entry.responseHeaders.toMultimap();
}
@Override public InputStream getBody() {
return in;
}
}
static class EntrySecureCacheResponse extends SecureCacheResponse implements SnapshotCacheResponse {
private final Entry entry;
private final DiskLruCache.Snapshot snapshot;
private final InputStream in;
@Override
public DiskLruCache.Snapshot getSnapshot() {
return snapshot;
}
public EntrySecureCacheResponse(Entry entry, DiskLruCache.Snapshot snapshot) {
this.entry = entry;
this.snapshot = snapshot;
this.in = newBodyInputStream(snapshot);
}
@Override public Map<String, List<String>> getHeaders() {
return entry.responseHeaders.toMultimap();
}
@Override public InputStream getBody() {
return in;
}
@Override public String getCipherSuite() {
return entry.cipherSuite;
}
@Override public List<Certificate> getServerCertificateChain()
throws SSLPeerUnverifiedException {
if (entry.peerCertificates == null || entry.peerCertificates.length == 0) {
throw new SSLPeerUnverifiedException(null);
}
return Arrays.asList(entry.peerCertificates.clone());
}
@Override public Principal getPeerPrincipal() throws SSLPeerUnverifiedException {
if (entry.peerCertificates == null || entry.peerCertificates.length == 0) {
throw new SSLPeerUnverifiedException(null);
}
return ((X509Certificate) entry.peerCertificates[0]).getSubjectX500Principal();
}
@Override public List<Certificate> getLocalCertificateChain() {
if (entry.localCertificates == null || entry.localCertificates.length == 0) {
return null;
}
return Arrays.asList(entry.localCertificates.clone());
}
@Override public Principal getLocalPrincipal() {
if (entry.localCertificates == null || entry.localCertificates.length == 0) {
return null;
}
return ((X509Certificate) entry.localCertificates[0]).getSubjectX500Principal();
}
}
public void clear() throws IOException {
if (cache != null) {
cache.delete();
open();
}
}
}