• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2011 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.volley.toolbox;
18 
19 import com.android.volley.Cache;
20 import com.android.volley.NetworkResponse;
21 
22 import org.apache.http.Header;
23 import org.apache.http.message.BasicHeader;
24 import org.junit.Before;
25 import org.junit.Test;
26 import org.junit.runner.RunWith;
27 import org.robolectric.RobolectricTestRunner;
28 
29 import java.text.DateFormat;
30 import java.text.SimpleDateFormat;
31 import java.util.Date;
32 import java.util.HashMap;
33 import java.util.Locale;
34 import java.util.Map;
35 
36 import static org.junit.Assert.*;
37 
38 @RunWith(RobolectricTestRunner.class)
39 public class HttpHeaderParserTest {
40 
41     private static long ONE_MINUTE_MILLIS = 1000L * 60;
42     private static long ONE_HOUR_MILLIS = 1000L * 60 * 60;
43     private static long ONE_DAY_MILLIS = ONE_HOUR_MILLIS * 24;
44     private static long ONE_WEEK_MILLIS = ONE_DAY_MILLIS * 7;
45 
46     private NetworkResponse response;
47     private Map<String, String> headers;
48 
setUp()49     @Before public void setUp() throws Exception {
50         headers = new HashMap<String, String>();
51         response = new NetworkResponse(0, null, headers, false);
52     }
53 
parseCacheHeaders_noHeaders()54     @Test public void parseCacheHeaders_noHeaders() {
55         Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
56 
57         assertNotNull(entry);
58         assertNull(entry.etag);
59         assertEquals(0, entry.serverDate);
60         assertEquals(0, entry.lastModified);
61         assertEquals(0, entry.ttl);
62         assertEquals(0, entry.softTtl);
63     }
64 
parseCacheHeaders_headersSet()65     @Test public void parseCacheHeaders_headersSet() {
66         headers.put("MyCustomHeader", "42");
67 
68         Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
69 
70         assertNotNull(entry);
71         assertNotNull(entry.responseHeaders);
72         assertEquals(1, entry.responseHeaders.size());
73         assertEquals("42", entry.responseHeaders.get("MyCustomHeader"));
74     }
75 
parseCacheHeaders_etag()76     @Test public void parseCacheHeaders_etag() {
77         headers.put("ETag", "Yow!");
78 
79         Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
80 
81         assertNotNull(entry);
82         assertEquals("Yow!", entry.etag);
83     }
84 
parseCacheHeaders_normalExpire()85     @Test public void parseCacheHeaders_normalExpire() {
86         long now = System.currentTimeMillis();
87         headers.put("Date", rfc1123Date(now));
88         headers.put("Last-Modified", rfc1123Date(now - ONE_DAY_MILLIS));
89         headers.put("Expires", rfc1123Date(now + ONE_HOUR_MILLIS));
90 
91         Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
92 
93         assertNotNull(entry);
94         assertNull(entry.etag);
95         assertEqualsWithin(entry.serverDate, now, ONE_MINUTE_MILLIS);
96         assertEqualsWithin(entry.lastModified, (now - ONE_DAY_MILLIS), ONE_MINUTE_MILLIS);
97         assertTrue(entry.softTtl >= (now + ONE_HOUR_MILLIS));
98         assertTrue(entry.ttl == entry.softTtl);
99     }
100 
parseCacheHeaders_expiresInPast()101     @Test public void parseCacheHeaders_expiresInPast() {
102         long now = System.currentTimeMillis();
103         headers.put("Date", rfc1123Date(now));
104         headers.put("Expires", rfc1123Date(now - ONE_HOUR_MILLIS));
105 
106         Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
107 
108         assertNotNull(entry);
109         assertNull(entry.etag);
110         assertEqualsWithin(entry.serverDate, now, ONE_MINUTE_MILLIS);
111         assertEquals(0, entry.ttl);
112         assertEquals(0, entry.softTtl);
113     }
114 
parseCacheHeaders_serverRelative()115     @Test public void parseCacheHeaders_serverRelative() {
116 
117         long now = System.currentTimeMillis();
118         // Set "current" date as one hour in the future
119         headers.put("Date", rfc1123Date(now + ONE_HOUR_MILLIS));
120         // TTL four hours in the future, so should be three hours from now
121         headers.put("Expires", rfc1123Date(now + 4 * ONE_HOUR_MILLIS));
122 
123         Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
124 
125         assertEqualsWithin(now + 3 * ONE_HOUR_MILLIS, entry.ttl, ONE_MINUTE_MILLIS);
126         assertEquals(entry.softTtl, entry.ttl);
127     }
128 
parseCacheHeaders_cacheControlOverridesExpires()129     @Test public void parseCacheHeaders_cacheControlOverridesExpires() {
130         long now = System.currentTimeMillis();
131         headers.put("Date", rfc1123Date(now));
132         headers.put("Expires", rfc1123Date(now + ONE_HOUR_MILLIS));
133         headers.put("Cache-Control", "public, max-age=86400");
134 
135         Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
136 
137         assertNotNull(entry);
138         assertNull(entry.etag);
139         assertEqualsWithin(now + ONE_DAY_MILLIS, entry.ttl, ONE_MINUTE_MILLIS);
140         assertEquals(entry.softTtl, entry.ttl);
141     }
142 
testParseCacheHeaders_staleWhileRevalidate()143     @Test public void testParseCacheHeaders_staleWhileRevalidate() {
144         long now = System.currentTimeMillis();
145         headers.put("Date", rfc1123Date(now));
146         headers.put("Expires", rfc1123Date(now + ONE_HOUR_MILLIS));
147 
148         // - max-age (entry.softTtl) indicates that the asset is fresh for 1 day
149         // - stale-while-revalidate (entry.ttl) indicates that the asset may
150         // continue to be served stale for up to additional 7 days
151         headers.put("Cache-Control", "max-age=86400, stale-while-revalidate=604800");
152 
153         Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
154 
155         assertNotNull(entry);
156         assertNull(entry.etag);
157         assertEqualsWithin(now + ONE_DAY_MILLIS, entry.softTtl, ONE_MINUTE_MILLIS);
158         assertEqualsWithin(now + ONE_DAY_MILLIS + ONE_WEEK_MILLIS, entry.ttl, ONE_MINUTE_MILLIS);
159     }
160 
parseCacheHeaders_cacheControlNoCache()161     @Test public void parseCacheHeaders_cacheControlNoCache() {
162         long now = System.currentTimeMillis();
163         headers.put("Date", rfc1123Date(now));
164         headers.put("Expires", rfc1123Date(now + ONE_HOUR_MILLIS));
165         headers.put("Cache-Control", "no-cache");
166 
167         Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
168 
169         assertNull(entry);
170     }
171 
parseCacheHeaders_cacheControlMustRevalidateNoMaxAge()172     @Test public void parseCacheHeaders_cacheControlMustRevalidateNoMaxAge() {
173         long now = System.currentTimeMillis();
174         headers.put("Date", rfc1123Date(now));
175         headers.put("Expires", rfc1123Date(now + ONE_HOUR_MILLIS));
176         headers.put("Cache-Control", "must-revalidate");
177 
178         Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
179         assertNotNull(entry);
180         assertNull(entry.etag);
181         assertEqualsWithin(now, entry.ttl, ONE_MINUTE_MILLIS);
182         assertEquals(entry.softTtl, entry.ttl);
183     }
184 
parseCacheHeaders_cacheControlMustRevalidateWithMaxAge()185     @Test public void parseCacheHeaders_cacheControlMustRevalidateWithMaxAge() {
186         long now = System.currentTimeMillis();
187         headers.put("Date", rfc1123Date(now));
188         headers.put("Expires", rfc1123Date(now + ONE_HOUR_MILLIS));
189         headers.put("Cache-Control", "must-revalidate, max-age=3600");
190 
191         Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
192         assertNotNull(entry);
193         assertNull(entry.etag);
194         assertEqualsWithin(now + ONE_HOUR_MILLIS, entry.ttl, ONE_MINUTE_MILLIS);
195         assertEquals(entry.softTtl, entry.ttl);
196     }
197 
parseCacheHeaders_cacheControlMustRevalidateWithMaxAgeAndStale()198     @Test public void parseCacheHeaders_cacheControlMustRevalidateWithMaxAgeAndStale() {
199         long now = System.currentTimeMillis();
200         headers.put("Date", rfc1123Date(now));
201         headers.put("Expires", rfc1123Date(now + ONE_HOUR_MILLIS));
202 
203         // - max-age (entry.softTtl) indicates that the asset is fresh for 1 day
204         // - stale-while-revalidate (entry.ttl) indicates that the asset may
205         // continue to be served stale for up to additional 7 days, but this is
206         // ignored in this case because of the must-revalidate header.
207         headers.put("Cache-Control",
208                 "must-revalidate, max-age=86400, stale-while-revalidate=604800");
209 
210         Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
211         assertNotNull(entry);
212         assertNull(entry.etag);
213         assertEqualsWithin(now + ONE_DAY_MILLIS, entry.softTtl, ONE_MINUTE_MILLIS);
214         assertEquals(entry.softTtl, entry.ttl);
215     }
216 
assertEqualsWithin(long expected, long value, long fudgeFactor)217     private void assertEqualsWithin(long expected, long value, long fudgeFactor) {
218         long diff = Math.abs(expected - value);
219         assertTrue(diff < fudgeFactor);
220     }
221 
222     private static String rfc1123Date(long millis) {
223         DateFormat df = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.ENGLISH);
224         return df.format(new Date(millis));
225     }
226 
227     // --------------------------
228 
229     @Test public void parseCharset() {
230         // Like the ones we usually see
231         headers.put("Content-Type", "text/plain; charset=utf-8");
232         assertEquals("utf-8", HttpHeaderParser.parseCharset(headers));
233 
234         // Charset specified, ignore default charset
235         headers.put("Content-Type", "text/plain; charset=utf-8");
236         assertEquals("utf-8", HttpHeaderParser.parseCharset(headers, "ISO-8859-1"));
237 
238         // Extra whitespace
239         headers.put("Content-Type", "text/plain;    charset=utf-8 ");
240         assertEquals("utf-8", HttpHeaderParser.parseCharset(headers));
241 
242         // Extra parameters
243         headers.put("Content-Type", "text/plain; charset=utf-8; frozzle=bar");
244         assertEquals("utf-8", HttpHeaderParser.parseCharset(headers));
245 
246         // No Content-Type header
247         headers.clear();
248         assertEquals("ISO-8859-1", HttpHeaderParser.parseCharset(headers));
249 
250         // No Content-Type header, use default charset
251         headers.clear();
252         assertEquals("utf-8", HttpHeaderParser.parseCharset(headers, "utf-8"));
253 
254         // Empty value
255         headers.put("Content-Type", "text/plain; charset=");
256         assertEquals("ISO-8859-1", HttpHeaderParser.parseCharset(headers));
257 
258         // None specified
259         headers.put("Content-Type", "text/plain");
260         assertEquals("ISO-8859-1", HttpHeaderParser.parseCharset(headers));
261 
262         // None charset specified, use default charset
263         headers.put("Content-Type", "application/json");
264         assertEquals("utf-8", HttpHeaderParser.parseCharset(headers, "utf-8"));
265 
266         // None specified, extra semicolon
267         headers.put("Content-Type", "text/plain;");
268         assertEquals("ISO-8859-1", HttpHeaderParser.parseCharset(headers));
269     }
270 
271     @Test public void parseCaseInsensitive() {
272 
273         long now = System.currentTimeMillis();
274 
275         Header[] headersArray = new Header[5];
276         headersArray[0] = new BasicHeader("eTAG", "Yow!");
277         headersArray[1] = new BasicHeader("DATE", rfc1123Date(now));
278         headersArray[2] = new BasicHeader("expires", rfc1123Date(now + ONE_HOUR_MILLIS));
279         headersArray[3] = new BasicHeader("cache-control", "public, max-age=86400");
280         headersArray[4] = new BasicHeader("content-type", "text/plain");
281 
282         Map<String, String> headers = BasicNetwork.convertHeaders(headersArray);
283         NetworkResponse response = new NetworkResponse(0, null, headers, false);
284         Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
285 
286         assertNotNull(entry);
287         assertEquals("Yow!", entry.etag);
288         assertEqualsWithin(now + ONE_DAY_MILLIS, entry.ttl, ONE_MINUTE_MILLIS);
289         assertEquals(entry.softTtl, entry.ttl);
290         assertEquals("ISO-8859-1", HttpHeaderParser.parseCharset(headers));
291     }
292 }
293