/** * Shadow - Anonymous web browser for Android devices * Copyright (C) 2009 Connell Gauld * * Thanks to University of Cambridge, * Alastair Beresford and Andrew Rice * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * version 2 as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ package uk.ac.cam.cl.dtg.android.tor.Shadow; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.util.Date; import java.util.HashMap; import java.util.Map; import org.apache.http.impl.cookie.DateParseException; import org.apache.http.impl.cookie.DateUtils; /** * Represents an individual item in the cache. * Intended to be in compliance with the HTTP/1.1 spec: * http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html * @author Connell Gauld * */ public class CacheObject { private HashMap<String, String[]> headers = null; private byte[] content = null; private int status = 0; private Date requestTime = null; private Date responseTime = null; private Date dateHeader = null; private Date expiresHeader = null; private long ageHeader = 0; private long maxageHeader = -1; private boolean cache = true; private String url = null; public CacheObject(String url, HashMap<String, String[]> headers, byte[] content, int status, Date requestTime, Date responseTime) { this.url = url; this.headers = headers; this.content = content; this.status = status; this.requestTime = requestTime; this.responseTime = responseTime; parseHeaders(); } public String getUrl() { return url; } public boolean isStale(Date now) { if (cache == false) return true; long freshness_lifetime; if (maxageHeader != -1) { freshness_lifetime = maxageHeader; } else { // If missing headers don't cache if ((expiresHeader == null) || (dateHeader == null)) return true; freshness_lifetime = (expiresHeader.getTime() - dateHeader.getTime())/1000; } return (getAge(now) > freshness_lifetime); } public String[] getConditionalHeader() { return new String[]{"If-Modified-Since", DateUtils.formatDate(requestTime)}; } private long getAge(Date now) { // Calculated in line with HTTP/1.1 specification: // http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.2.3 long apparent_age = 0; if (dateHeader != null) { apparent_age =(responseTime.getTime() - dateHeader.getTime()) / 1000; if (apparent_age < 0) apparent_age = 0; } long corrected_received_age; if (apparent_age > ageHeader) corrected_received_age = apparent_age; else corrected_received_age = ageHeader; long response_delay = (responseTime.getTime() - requestTime.getTime())/1000; long corrected_initial_age = corrected_received_age + response_delay; long resident_time = (now.getTime() - responseTime.getTime()) / 1000; return corrected_initial_age + resident_time; } public long getContentLength() { if (content != null) return content.length; else return 0; } public Map<String, String[]> getHeaders() { return headers; } public InputStream getNewInputStream() { return new ByteArrayInputStream(content); } public int getStatus() { return status; } public long getSize() { if (content == null) return 0; else return content.length; } private void parseHeaders() { // See HTTP/1.1 spec // http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html if (headers.containsKey("expires")) { try { expiresHeader = DateUtils.parseDate(headers.get("expires")[1]); } catch (DateParseException e) {} } if (headers.containsKey("date")) { try { dateHeader = DateUtils.parseDate(headers.get("date")[1]); } catch (DateParseException e) {} } if (headers.containsKey("age")) { try { ageHeader = Integer.parseInt(headers.get("age")[1]); } catch (NumberFormatException e) {} } if (headers.containsKey("cache-control")) { String rhs = headers.get("cache-control")[1]; int pos = rhs.indexOf("max-age="); if (pos != -1) { String maxageStr = rhs.substring(pos + "max-age=".length()); try { maxageHeader = Integer.parseInt(maxageStr); } catch (NumberFormatException e) {} } else { // Probably Cache-Control: no-cache cache = false; } } } }