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; 459324df1b8046548587ffec89ec755264f6fbb097Ralph Bergmann long lastModified = 0; 46d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru long serverExpires = 0; 47d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru long softExpire = 0; 488e33d93dc1aae9bb9dbde3b80af8a76ba28f0e19Ralph Bergmann long finalExpire = 0; 49d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru long maxAge = 0; 508e33d93dc1aae9bb9dbde3b80af8a76ba28f0e19Ralph Bergmann long staleWhileRevalidate = 0; 51d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru boolean hasCacheControl = false; 52782b52fd6f5c62f5b38148432acbc4e79351fd0bDave Santoro boolean mustRevalidate = false; 53d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 54d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru String serverEtag = null; 55d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru String headerValue; 56d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 57d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru headerValue = headers.get("Date"); 58d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (headerValue != null) { 59d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru serverDate = parseDateAsEpoch(headerValue); 60d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 61d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 62d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru headerValue = headers.get("Cache-Control"); 63d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (headerValue != null) { 64d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru hasCacheControl = true; 65d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru String[] tokens = headerValue.split(","); 66d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru for (int i = 0; i < tokens.length; i++) { 67d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru String token = tokens[i].trim(); 68d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (token.equals("no-cache") || token.equals("no-store")) { 69d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return null; 70d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } else if (token.startsWith("max-age=")) { 71d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru try { 72d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru maxAge = Long.parseLong(token.substring(8)); 73d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } catch (Exception e) { 74d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 758e33d93dc1aae9bb9dbde3b80af8a76ba28f0e19Ralph Bergmann } else if (token.startsWith("stale-while-revalidate=")) { 768e33d93dc1aae9bb9dbde3b80af8a76ba28f0e19Ralph Bergmann try { 778e33d93dc1aae9bb9dbde3b80af8a76ba28f0e19Ralph Bergmann staleWhileRevalidate = Long.parseLong(token.substring(23)); 788e33d93dc1aae9bb9dbde3b80af8a76ba28f0e19Ralph Bergmann } catch (Exception e) { 798e33d93dc1aae9bb9dbde3b80af8a76ba28f0e19Ralph Bergmann } 80d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } else if (token.equals("must-revalidate") || token.equals("proxy-revalidate")) { 81782b52fd6f5c62f5b38148432acbc4e79351fd0bDave Santoro mustRevalidate = true; 82d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 83d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 84d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 85d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 86d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru headerValue = headers.get("Expires"); 87d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (headerValue != null) { 88d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru serverExpires = parseDateAsEpoch(headerValue); 89d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 90d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 919324df1b8046548587ffec89ec755264f6fbb097Ralph Bergmann headerValue = headers.get("Last-Modified"); 929324df1b8046548587ffec89ec755264f6fbb097Ralph Bergmann if (headerValue != null) { 939324df1b8046548587ffec89ec755264f6fbb097Ralph Bergmann lastModified = parseDateAsEpoch(headerValue); 949324df1b8046548587ffec89ec755264f6fbb097Ralph Bergmann } 959324df1b8046548587ffec89ec755264f6fbb097Ralph Bergmann 96d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru serverEtag = headers.get("ETag"); 97d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 98d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru // Cache-Control takes precedence over an Expires header, even if both exist and Expires 99d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru // is more restrictive. 100d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (hasCacheControl) { 101d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru softExpire = now + maxAge * 1000; 102782b52fd6f5c62f5b38148432acbc4e79351fd0bDave Santoro finalExpire = mustRevalidate 103782b52fd6f5c62f5b38148432acbc4e79351fd0bDave Santoro ? softExpire 104782b52fd6f5c62f5b38148432acbc4e79351fd0bDave Santoro : softExpire + staleWhileRevalidate * 1000; 105d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } else if (serverDate > 0 && serverExpires >= serverDate) { 106d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru // Default semantic for Expire header in HTTP specification is softExpire. 107d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru softExpire = now + (serverExpires - serverDate); 1088e33d93dc1aae9bb9dbde3b80af8a76ba28f0e19Ralph Bergmann finalExpire = softExpire; 109d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 110d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 111d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru Cache.Entry entry = new Cache.Entry(); 112d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru entry.data = response.data; 113d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru entry.etag = serverEtag; 114d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru entry.softTtl = softExpire; 1158e33d93dc1aae9bb9dbde3b80af8a76ba28f0e19Ralph Bergmann entry.ttl = finalExpire; 116d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru entry.serverDate = serverDate; 1179324df1b8046548587ffec89ec755264f6fbb097Ralph Bergmann entry.lastModified = lastModified; 118e48f4430bfd3030350aa5ba827b449c37e2fadc9Jean-Baptiste Queru entry.responseHeaders = headers; 119d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 120d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return entry; 121d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 122d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 123d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 124d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru * Parse date in RFC1123 format, and return its value as epoch 125d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 126d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru public static long parseDateAsEpoch(String dateStr) { 127d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru try { 128d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru // Parse date in RFC1123 format if this header contains one 129d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return DateUtils.parseDate(dateStr).getTime(); 130d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } catch (DateParseException e) { 131d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru // Date in invalid format, fallback to 0 132d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return 0; 133d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 134d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 135d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 136d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru /** 1376bafd7d28fc7947f263feb7134fc8a70084357c3Zdeněk Kořán * Retrieve a charset from headers 1386bafd7d28fc7947f263feb7134fc8a70084357c3Zdeněk Kořán * 1396bafd7d28fc7947f263feb7134fc8a70084357c3Zdeněk Kořán * @param headers An {@link java.util.Map} of headers 1406bafd7d28fc7947f263feb7134fc8a70084357c3Zdeněk Kořán * @param defaultCharset Charset to return if none can be found 1416bafd7d28fc7947f263feb7134fc8a70084357c3Zdeněk Kořán * @return Returns the charset specified in the Content-Type of this header, 1426bafd7d28fc7947f263feb7134fc8a70084357c3Zdeněk Kořán * or the defaultCharset if none can be found. 143d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru */ 1446bafd7d28fc7947f263feb7134fc8a70084357c3Zdeněk Kořán public static String parseCharset(Map<String, String> headers, String defaultCharset) { 145d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru String contentType = headers.get(HTTP.CONTENT_TYPE); 146d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (contentType != null) { 147d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru String[] params = contentType.split(";"); 148d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru for (int i = 1; i < params.length; i++) { 149d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru String[] pair = params[i].trim().split("="); 150d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (pair.length == 2) { 151d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru if (pair[0].equals("charset")) { 152d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru return pair[1]; 153d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 154d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 155d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 156d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 157d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru 1586bafd7d28fc7947f263feb7134fc8a70084357c3Zdeněk Kořán return defaultCharset; 1596bafd7d28fc7947f263feb7134fc8a70084357c3Zdeněk Kořán } 1606bafd7d28fc7947f263feb7134fc8a70084357c3Zdeněk Kořán 1616bafd7d28fc7947f263feb7134fc8a70084357c3Zdeněk Kořán /** 1626bafd7d28fc7947f263feb7134fc8a70084357c3Zdeněk Kořán * Returns the charset specified in the Content-Type of this header, 1636bafd7d28fc7947f263feb7134fc8a70084357c3Zdeněk Kořán * or the HTTP default (ISO-8859-1) if none can be found. 1646bafd7d28fc7947f263feb7134fc8a70084357c3Zdeněk Kořán */ 1656bafd7d28fc7947f263feb7134fc8a70084357c3Zdeněk Kořán public static String parseCharset(Map<String, String> headers) { 1666bafd7d28fc7947f263feb7134fc8a70084357c3Zdeněk Kořán return parseCharset(headers, HTTP.DEFAULT_CONTENT_CHARSET); 167d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru } 168d56b88ae161057e848e7410d1b9ce5b0b8c427fcJean-Baptiste Queru} 169