1d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru/* 2d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Copyright (C) 2011 The Android Open Source Project 3d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * 4d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Licensed under the Apache License, Version 2.0 (the "License"); 5d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * you may not use this file except in compliance with the License. 6d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * You may obtain a copy of the License at 7d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * 8d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * http://www.apache.org/licenses/LICENSE-2.0 9d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * 10d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Unless required by applicable law or agreed to in writing, software 11d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * distributed under the License is distributed on an "AS IS" BASIS, 12d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * See the License for the specific language governing permissions and 14d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * limitations under the License. 15d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 16d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 17d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Querupackage com.android.volley.toolbox; 18d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 19d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport com.android.volley.Cache; 20d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport com.android.volley.NetworkResponse; 21d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 22d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport org.apache.http.impl.cookie.DateParseException; 23d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport org.apache.http.impl.cookie.DateUtils; 24d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport org.apache.http.protocol.HTTP; 25d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 26d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queruimport java.util.Map; 27d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 28d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru/** 29d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Utility methods for parsing HTTP headers. 30d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 31d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Querupublic class HttpHeaderParser { 32d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 33d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 34d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Extracts a {@link Cache.Entry} from a {@link NetworkResponse}. 35d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * 36d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @param response The network response to parse headers from 37d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * @return a cache entry for the given response, or null if the response is not cacheable. 38d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 39d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public static Cache.Entry parseCacheHeaders(NetworkResponse response) { 40d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru long now = System.currentTimeMillis(); 41d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 42d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru Map<String, String> headers = response.headers; 43d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 44d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru long serverDate = 0; 45d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru long serverExpires = 0; 46d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru long softExpire = 0; 47d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru long maxAge = 0; 48d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru boolean hasCacheControl = false; 49d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 50d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru String serverEtag = null; 51d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru String headerValue; 52d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 53d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru headerValue = headers.get("Date"); 54d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (headerValue != null) { 55d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru serverDate = parseDateAsEpoch(headerValue); 56d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 57d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 58d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru headerValue = headers.get("Cache-Control"); 59d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (headerValue != null) { 60d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru hasCacheControl = true; 61d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru String[] tokens = headerValue.split(","); 62d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru for (int i = 0; i < tokens.length; i++) { 63d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru String token = tokens[i].trim(); 64d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (token.equals("no-cache") || token.equals("no-store")) { 65d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return null; 66d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } else if (token.startsWith("max-age=")) { 67d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru try { 68d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru maxAge = Long.parseLong(token.substring(8)); 69d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } catch (Exception e) { 70d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 71d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) { 72d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru maxAge = 0; 73d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 74d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 75d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 76d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 77d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru headerValue = headers.get("Expires"); 78d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (headerValue != null) { 79d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru serverExpires = parseDateAsEpoch(headerValue); 80d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 81d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 82d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru serverEtag = headers.get("ETag"); 83d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 84d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru // Cache-Control takes precedence over an Expires header, even if both exist and Expires 85d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru // is more restrictive. 86d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (hasCacheControl) { 87d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru softExpire = now + maxAge * 1000; 88d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } else if (serverDate > 0 && serverExpires >= serverDate) { 89d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru // Default semantic for Expire header in HTTP specification is softExpire. 90d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru softExpire = now + (serverExpires - serverDate); 91d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 92d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 93d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru Cache.Entry entry = new Cache.Entry(); 94d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru entry.data = response.data; 95d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru entry.etag = serverEtag; 96d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru entry.softTtl = softExpire; 97d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru entry.ttl = entry.softTtl; 98d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru entry.serverDate = serverDate; 99e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru entry.responseHeaders = headers; 100d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 101d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return entry; 102d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 103d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 104d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 105d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Parse date in RFC1123 format, and return its value as epoch 106d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 107d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public static long parseDateAsEpoch(String dateStr) { 108d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru try { 109d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru // Parse date in RFC1123 format if this header contains one 110d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return DateUtils.parseDate(dateStr).getTime(); 111d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } catch (DateParseException e) { 112d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru // Date in invalid format, fallback to 0 113d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return 0; 114d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 115d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 116d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 117d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 118d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Returns the charset specified in the Content-Type of this header, 119d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * or the HTTP default (ISO-8859-1) if none can be found. 120d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 121d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public static String parseCharset(Map<String, String> headers) { 122d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru String contentType = headers.get(HTTP.CONTENT_TYPE); 123d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (contentType != null) { 124d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru String[] params = contentType.split(";"); 125d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru for (int i = 1; i < params.length; i++) { 126d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru String[] pair = params[i].trim().split("="); 127d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (pair.length == 2) { 128d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (pair[0].equals("charset")) { 129d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return pair[1]; 130d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 131d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 132d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 133d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 134d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 135d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return HTTP.DEFAULT_CONTENT_CHARSET; 136d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 137d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru} 138