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_etag() {
59        headers.put("ETag", "Yow!");
60
61        Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
62
63        assertNotNull(entry);
64        assertEquals("Yow!", entry.etag);
65    }
66
67    public void testParseCacheHeaders_normalExpire() {
68        long now = System.currentTimeMillis();
69        headers.put("Date", rfc1123Date(now));
70        headers.put("Expires", rfc1123Date(now + ONE_HOUR_MILLIS));
71
72        Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
73
74        assertNotNull(entry);
75        assertNull(entry.etag);
76        assertEqualsWithin(entry.serverDate, now, ONE_MINUTE_MILLIS);
77        assertTrue(entry.softTtl >= (now + ONE_HOUR_MILLIS));
78        assertTrue(entry.ttl == entry.softTtl);
79    }
80
81    public void testParseCacheHeaders_expiresInPast() {
82        long now = System.currentTimeMillis();
83        headers.put("Date", rfc1123Date(now));
84        headers.put("Expires", rfc1123Date(now - ONE_HOUR_MILLIS));
85
86        Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
87
88        assertNotNull(entry);
89        assertNull(entry.etag);
90        assertEqualsWithin(entry.serverDate, now, ONE_MINUTE_MILLIS);
91        assertEquals(0, entry.ttl);
92        assertEquals(0, entry.softTtl);
93    }
94
95    public void testParseCacheHeaders_serverRelative() {
96
97        long now = System.currentTimeMillis();
98        // Set "current" date as one hour in the future
99        headers.put("Date", rfc1123Date(now + ONE_HOUR_MILLIS));
100        // TTL four hours in the future, so should be three hours from now
101        headers.put("Expires", rfc1123Date(now + 4 * ONE_HOUR_MILLIS));
102
103        Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
104
105        assertEqualsWithin(now + 3 * ONE_HOUR_MILLIS, entry.ttl, ONE_MINUTE_MILLIS);
106        assertEquals(entry.softTtl, entry.ttl);
107    }
108
109    public void testParseCacheHeaders_cacheControlOverridesExpires() {
110        long now = System.currentTimeMillis();
111        headers.put("Date", rfc1123Date(now));
112        headers.put("Expires", rfc1123Date(now + ONE_HOUR_MILLIS));
113        headers.put("Cache-Control", "public, max-age=86400");
114
115        Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
116
117        assertNotNull(entry);
118        assertNull(entry.etag);
119        assertEqualsWithin(now + 24 * ONE_HOUR_MILLIS, entry.ttl, ONE_MINUTE_MILLIS);
120        assertEquals(entry.softTtl, entry.ttl);
121    }
122
123    public void testParseCacheHeaders_cacheControlNoCache() {
124        long now = System.currentTimeMillis();
125        headers.put("Date", rfc1123Date(now));
126        headers.put("Expires", rfc1123Date(now + ONE_HOUR_MILLIS));
127        headers.put("Cache-Control", "no-cache");
128
129        Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
130
131        assertNull(entry);
132    }
133
134    public void testParseCacheHeaders_cacheControlMustRevalidate() {
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", "must-revalidate");
139
140        Cache.Entry entry = HttpHeaderParser.parseCacheHeaders(response);
141
142        assertNotNull(entry);
143        assertNull(entry.etag);
144        assertEqualsWithin(now, entry.ttl, ONE_MINUTE_MILLIS);
145        assertEquals(entry.softTtl, entry.ttl);
146    }
147
148    private void assertEqualsWithin(long expected, long value, long fudgeFactor) {
149        long diff = Math.abs(expected - value);
150        assertTrue(diff < fudgeFactor);
151    }
152
153    private static String rfc1123Date(long millis) {
154        DateFormat df = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz");
155        return df.format(new Date(millis));
156    }
157
158    // --------------------------
159
160    public void testParseCharset() {
161        // Like the ones we usually see
162        headers.put("Content-Type", "text/plain; charset=utf-8");
163        assertEquals("utf-8", HttpHeaderParser.parseCharset(headers));
164
165        // Extra whitespace
166        headers.put("Content-Type", "text/plain;    charset=utf-8 ");
167        assertEquals("utf-8", HttpHeaderParser.parseCharset(headers));
168
169        // Extra parameters
170        headers.put("Content-Type", "text/plain; charset=utf-8; frozzle=bar");
171        assertEquals("utf-8", HttpHeaderParser.parseCharset(headers));
172
173        // No Content-Type header
174        headers.clear();
175        assertEquals("ISO-8859-1", HttpHeaderParser.parseCharset(headers));
176
177        // Empty value
178        headers.put("Content-Type", "text/plain; charset=");
179        assertEquals("ISO-8859-1", HttpHeaderParser.parseCharset(headers));
180
181        // None specified
182        headers.put("Content-Type", "text/plain");
183        assertEquals("ISO-8859-1", HttpHeaderParser.parseCharset(headers));
184
185        // None specified, extra semicolon
186        headers.put("Content-Type", "text/plain;");
187        assertEquals("ISO-8859-1", HttpHeaderParser.parseCharset(headers));
188    }
189}
190