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
17package com.android.volley.toolbox;
18
19import android.test.suitebuilder.annotation.SmallTest;
20
21import com.android.volley.Cache;
22import com.android.volley.NetworkResponse;
23
24import java.text.DateFormat;
25import java.text.SimpleDateFormat;
26import java.util.Date;
27import java.util.HashMap;
28import java.util.Map;
29
30import junit.framework.TestCase;
31
32@SmallTest
33public class HttpHeaderParserTest extends TestCase {
34
35    private static long ONE_MINUTE_MILLIS = 1000L * 60;
36    private static long ONE_HOUR_MILLIS = 1000L * 60 * 60;
37
38    private NetworkResponse response;
39    private Map<String, String> headers;
40
41    @Override
42    protected void setUp() throws Exception {
43        super.setUp();
44        headers = new HashMap<String, String>();
45        response = new NetworkResponse(0, null, headers, false);
46    }
47
48    public void testParseCacheHeaders_noHeaders() {
49        Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
50
51        assertNotNull(entry);
52        assertNull(entry.etag);
53        assertEquals(0, entry.serverDate);
54        assertEquals(0, entry.ttl);
55        assertEquals(0, entry.softTtl);
56    }
57
58    public void testParseCacheHeaders_headersSet() {
59        headers.put("MyCustomHeader", "42");
60
61        Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
62
63        assertNotNull(entry);
64        assertNotNull(entry.responseHeaders);
65        assertEquals(1, entry.responseHeaders.size());
66        assertEquals("42", entry.responseHeaders.get("MyCustomHeader"));
67    }
68
69    public void testParseCacheHeaders_etag() {
70        headers.put("ETag", "Yow!");
71
72        Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
73
74        assertNotNull(entry);
75        assertEquals("Yow!", entry.etag);
76    }
77
78    public void testParseCacheHeaders_normalExpire() {
79        long now = System.currentTimeMillis();
80        headers.put("Date", rfc1123Date(now));
81        headers.put("Expires", rfc1123Date(now + ONE_HOUR_MILLIS));
82
83        Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
84
85        assertNotNull(entry);
86        assertNull(entry.etag);
87        assertEqualsWithin(entry.serverDate, now, ONE_MINUTE_MILLIS);
88        assertTrue(entry.softTtl >= (now + ONE_HOUR_MILLIS));
89        assertTrue(entry.ttl == entry.softTtl);
90    }
91
92    public void testParseCacheHeaders_expiresInPast() {
93        long now = System.currentTimeMillis();
94        headers.put("Date", rfc1123Date(now));
95        headers.put("Expires", rfc1123Date(now - ONE_HOUR_MILLIS));
96
97        Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
98
99        assertNotNull(entry);
100        assertNull(entry.etag);
101        assertEqualsWithin(entry.serverDate, now, ONE_MINUTE_MILLIS);
102        assertEquals(0, entry.ttl);
103        assertEquals(0, entry.softTtl);
104    }
105
106    public void testParseCacheHeaders_serverRelative() {
107
108        long now = System.currentTimeMillis();
109        // Set "current" date as one hour in the future
110        headers.put("Date", rfc1123Date(now + ONE_HOUR_MILLIS));
111        // TTL four hours in the future, so should be three hours from now
112        headers.put("Expires", rfc1123Date(now + 4 * ONE_HOUR_MILLIS));
113
114        Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
115
116        assertEqualsWithin(now + 3 * ONE_HOUR_MILLIS, entry.ttl, ONE_MINUTE_MILLIS);
117        assertEquals(entry.softTtl, entry.ttl);
118    }
119
120    public void testParseCacheHeaders_cacheControlOverridesExpires() {
121        long now = System.currentTimeMillis();
122        headers.put("Date", rfc1123Date(now));
123        headers.put("Expires", rfc1123Date(now + ONE_HOUR_MILLIS));
124        headers.put("Cache-Control", "public, max-age=86400");
125
126        Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
127
128        assertNotNull(entry);
129        assertNull(entry.etag);
130        assertEqualsWithin(now + 24 * ONE_HOUR_MILLIS, entry.ttl, ONE_MINUTE_MILLIS);
131        assertEquals(entry.softTtl, entry.ttl);
132    }
133
134    public void testParseCacheHeaders_cacheControlNoCache() {
135        long now = System.currentTimeMillis();
136        headers.put("Date", rfc1123Date(now));
137        headers.put("Expires", rfc1123Date(now + ONE_HOUR_MILLIS));
138        headers.put("Cache-Control", "no-cache");
139
140        Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
141
142        assertNull(entry);
143    }
144
145    public void testParseCacheHeaders_cacheControlMustRevalidate() {
146        long now = System.currentTimeMillis();
147        headers.put("Date", rfc1123Date(now));
148        headers.put("Expires", rfc1123Date(now + ONE_HOUR_MILLIS));
149        headers.put("Cache-Control", "must-revalidate");
150
151        Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
152
153        assertNotNull(entry);
154        assertNull(entry.etag);
155        assertEqualsWithin(now, entry.ttl, ONE_MINUTE_MILLIS);
156        assertEquals(entry.softTtl, entry.ttl);
157    }
158
159    private void assertEqualsWithin(long expected, long value, long fudgeFactor) {
160        long diff = Math.abs(expected - value);
161        assertTrue(diff < fudgeFactor);
162    }
163
164    private static String rfc1123Date(long millis) {
165        DateFormat df = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz");
166        return df.format(new Date(millis));
167    }
168
169    // --------------------------
170
171    public void testParseCharset() {
172        // Like the ones we usually see
173        headers.put("Content-Type", "text/plain; charset=utf-8");
174        assertEquals("utf-8", HttpHeaderParser.parseCharset(headers));
175
176        // Extra whitespace
177        headers.put("Content-Type", "text/plain;    charset=utf-8 ");
178        assertEquals("utf-8", HttpHeaderParser.parseCharset(headers));
179
180        // Extra parameters
181        headers.put("Content-Type", "text/plain; charset=utf-8; frozzle=bar");
182        assertEquals("utf-8", HttpHeaderParser.parseCharset(headers));
183
184        // No Content-Type header
185        headers.clear();
186        assertEquals("ISO-8859-1", HttpHeaderParser.parseCharset(headers));
187
188        // Empty value
189        headers.put("Content-Type", "text/plain; charset=");
190        assertEquals("ISO-8859-1", HttpHeaderParser.parseCharset(headers));
191
192        // None specified
193        headers.put("Content-Type", "text/plain");
194        assertEquals("ISO-8859-1", HttpHeaderParser.parseCharset(headers));
195
196        // None specified, extra semicolon
197        headers.put("Content-Type", "text/plain;");
198        assertEquals("ISO-8859-1", HttpHeaderParser.parseCharset(headers));
199    }
200}
201