13713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick/* 23713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * Copyright (C) 2011 The Android Open Source Project 33713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * 43713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * Licensed under the Apache License, Version 2.0 (the "License"); 53713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * you may not use this file except in compliance with the License. 63713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * You may obtain a copy of the License at 73713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * 83713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * http://www.apache.org/licenses/LICENSE-2.0 93713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * 103713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * Unless required by applicable law or agreed to in writing, software 113713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * distributed under the License is distributed on an "AS IS" BASIS, 123713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 133713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * See the License for the specific language governing permissions and 143713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * limitations under the License. 153713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick */ 163713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 173713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrickpackage com.android.volley.toolbox; 183713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 193713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrickimport com.android.volley.Cache; 203713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrickimport com.android.volley.NetworkResponse; 213713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 223713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrickimport org.apache.http.impl.cookie.DateParseException; 233713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrickimport org.apache.http.impl.cookie.DateUtils; 243713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrickimport org.apache.http.protocol.HTTP; 253713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 263713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrickimport java.util.Map; 273713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 283713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick/** 293713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * Utility methods for parsing HTTP headers. 303713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick */ 313713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrickpublic class HttpHeaderParser { 323713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 333713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick /** 343713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * Extracts a {@link Cache.Entry} from a {@link NetworkResponse}. 353713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * 363713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * @param response The network response to parse headers from 37ccde61ed8922020f05bd43a80dfcc130c99c00f0Scott Barta * @return a cache entry for the given response, or null if the response is not cacheable. 383713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick */ 393713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick public static Cache.Entry parseCacheHeaders(NetworkResponse response) { 403713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick long now = System.currentTimeMillis(); 413713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 423713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick Map<String, String> headers = response.headers; 433713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 443713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick long serverDate = 0; 453713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick long serverExpires = 0; 463713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick long softExpire = 0; 47ccde61ed8922020f05bd43a80dfcc130c99c00f0Scott Barta long maxAge = 0; 48ccde61ed8922020f05bd43a80dfcc130c99c00f0Scott Barta boolean hasCacheControl = false; 493713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 503713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick String serverEtag = null; 513713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick String headerValue; 523713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 533713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick headerValue = headers.get("Date"); 543713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick if (headerValue != null) { 553713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick serverDate = parseDateAsEpoch(headerValue); 563713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick } 573713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 58ccde61ed8922020f05bd43a80dfcc130c99c00f0Scott Barta headerValue = headers.get("Cache-Control"); 59ccde61ed8922020f05bd43a80dfcc130c99c00f0Scott Barta if (headerValue != null) { 60ccde61ed8922020f05bd43a80dfcc130c99c00f0Scott Barta hasCacheControl = true; 61ccde61ed8922020f05bd43a80dfcc130c99c00f0Scott Barta String[] tokens = headerValue.split(","); 62ccde61ed8922020f05bd43a80dfcc130c99c00f0Scott Barta for (int i = 0; i < tokens.length; i++) { 63ccde61ed8922020f05bd43a80dfcc130c99c00f0Scott Barta String token = tokens[i].trim(); 64ccde61ed8922020f05bd43a80dfcc130c99c00f0Scott Barta if (token.equals("no-cache") || token.equals("no-store")) { 65ccde61ed8922020f05bd43a80dfcc130c99c00f0Scott Barta return null; 66ccde61ed8922020f05bd43a80dfcc130c99c00f0Scott Barta } else if (token.startsWith("max-age=")) { 67ccde61ed8922020f05bd43a80dfcc130c99c00f0Scott Barta try { 68ccde61ed8922020f05bd43a80dfcc130c99c00f0Scott Barta maxAge = Long.parseLong(token.substring(8)); 69ccde61ed8922020f05bd43a80dfcc130c99c00f0Scott Barta } catch (Exception e) { 70ccde61ed8922020f05bd43a80dfcc130c99c00f0Scott Barta } 71ccde61ed8922020f05bd43a80dfcc130c99c00f0Scott Barta } else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) { 72ccde61ed8922020f05bd43a80dfcc130c99c00f0Scott Barta maxAge = 0; 73ccde61ed8922020f05bd43a80dfcc130c99c00f0Scott Barta } 74ccde61ed8922020f05bd43a80dfcc130c99c00f0Scott Barta } 75ccde61ed8922020f05bd43a80dfcc130c99c00f0Scott Barta } 76ccde61ed8922020f05bd43a80dfcc130c99c00f0Scott Barta 773713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick headerValue = headers.get("Expires"); 783713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick if (headerValue != null) { 793713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick serverExpires = parseDateAsEpoch(headerValue); 803713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick } 813713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 823713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick serverEtag = headers.get("ETag"); 833713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 84ccde61ed8922020f05bd43a80dfcc130c99c00f0Scott Barta // Cache-Control takes precedence over an Expires header, even if both exist and Expires 85ccde61ed8922020f05bd43a80dfcc130c99c00f0Scott Barta // is more restrictive. 86ccde61ed8922020f05bd43a80dfcc130c99c00f0Scott Barta if (hasCacheControl) { 87ccde61ed8922020f05bd43a80dfcc130c99c00f0Scott Barta softExpire = now + maxAge * 1000; 88ccde61ed8922020f05bd43a80dfcc130c99c00f0Scott Barta } else if (serverDate > 0 && serverExpires >= serverDate) { 893713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick // Default semantic for Expire header in HTTP specification is softExpire. 903713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick softExpire = now + (serverExpires - serverDate); 913713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick } 923713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 933713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick Cache.Entry entry = new Cache.Entry(); 943713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick entry.data = response.data; 953713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick entry.etag = serverEtag; 963713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick entry.softTtl = softExpire; 973713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick entry.ttl = entry.softTtl; 983713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick entry.serverDate = serverDate; 993713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 1003713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick return entry; 1013713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick } 1023713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 1033713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick /** 1043713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * Parse date in RFC1123 format, and return its value as epoch 1053713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick */ 1063713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick public static long parseDateAsEpoch(String dateStr) { 1073713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick try { 1083713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick // Parse date in RFC1123 format if this header contains one 1093713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick return DateUtils.parseDate(dateStr).getTime(); 1103713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick } catch (DateParseException e) { 1113713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick // Date in invalid format, fallback to 0 1123713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick return 0; 1133713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick } 1143713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick } 1153713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 1163713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick /** 1173713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * Returns the charset specified in the Content-Type of this header, 1183713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick * or the HTTP default (ISO-8859-1) if none can be found. 1193713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick */ 1203713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick public static String parseCharset(Map<String, String> headers) { 1213713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick String contentType = headers.get(HTTP.CONTENT_TYPE); 1223713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick if (contentType != null) { 1233713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick String[] params = contentType.split(";"); 1243713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick for (int i = 1; i < params.length; i++) { 1253713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick String[] pair = params[i].trim().split("="); 1263713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick if (pair.length == 2) { 1273713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick if (pair[0].equals("charset")) { 1283713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick return pair[1]; 1293713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick } 1303713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick } 1313713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick } 1323713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick } 1333713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick 1343713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick return HTTP.DEFAULT_CONTENT_CHARSET; 1353713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick } 1363713094c56d25e25df2a508dbee4aea869ffdea1Ficus Kirkpatrick} 137