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 libcore.net.http;
18
19import java.net.URI;
20import java.util.Date;
21import java.util.List;
22import java.util.Map;
23
24/**
25 * Parsed HTTP request headers.
26 */
27public final class RequestHeaders {
28    private final URI uri;
29    private final RawHeaders headers;
30
31    /** Don't use a cache to satisfy this request. */
32    private boolean noCache;
33    private int maxAgeSeconds = -1;
34    private int maxStaleSeconds = -1;
35    private int minFreshSeconds = -1;
36
37    /**
38     * This field's name "only-if-cached" is misleading. It actually means "do
39     * not use the network". It is set by a client who only wants to make a
40     * request if it can be fully satisfied by the cache. Cached responses that
41     * would require validation (ie. conditional gets) are not permitted if this
42     * header is set.
43     */
44    private boolean onlyIfCached;
45
46    /**
47     * True if the request contains an authorization field. Although this isn't
48     * necessarily a shared cache, it follows the spec's strict requirements for
49     * shared caches.
50     */
51    private boolean hasAuthorization;
52
53    private int contentLength = -1;
54    private String transferEncoding;
55    private String userAgent;
56    private String host;
57    private String connection;
58    private String acceptEncoding;
59    private String contentType;
60    private String ifModifiedSince;
61    private String ifNoneMatch;
62    private String proxyAuthorization;
63
64    public RequestHeaders(URI uri, RawHeaders headers) {
65        this.uri = uri;
66        this.headers = headers;
67
68        HeaderParser.CacheControlHandler handler = new HeaderParser.CacheControlHandler() {
69            @Override public void handle(String directive, String parameter) {
70                if ("no-cache".equalsIgnoreCase(directive)) {
71                    noCache = true;
72                } else if ("max-age".equalsIgnoreCase(directive)) {
73                    maxAgeSeconds = HeaderParser.parseSeconds(parameter);
74                } else if ("max-stale".equalsIgnoreCase(directive)) {
75                    maxStaleSeconds = HeaderParser.parseSeconds(parameter);
76                } else if ("min-fresh".equalsIgnoreCase(directive)) {
77                    minFreshSeconds = HeaderParser.parseSeconds(parameter);
78                } else if ("only-if-cached".equalsIgnoreCase(directive)) {
79                    onlyIfCached = true;
80                }
81            }
82        };
83
84        for (int i = 0; i < headers.length(); i++) {
85            String fieldName = headers.getFieldName(i);
86            String value = headers.getValue(i);
87            if ("Cache-Control".equalsIgnoreCase(fieldName)) {
88                HeaderParser.parseCacheControl(value, handler);
89            } else if ("Pragma".equalsIgnoreCase(fieldName)) {
90                if ("no-cache".equalsIgnoreCase(value)) {
91                    noCache = true;
92                }
93            } else if ("If-None-Match".equalsIgnoreCase(fieldName)) {
94                ifNoneMatch = value;
95            } else if ("If-Modified-Since".equalsIgnoreCase(fieldName)) {
96                ifModifiedSince = value;
97            } else if ("Authorization".equalsIgnoreCase(fieldName)) {
98                hasAuthorization = true;
99            } else if ("Content-Length".equalsIgnoreCase(fieldName)) {
100                try {
101                    contentLength = Integer.parseInt(value);
102                } catch (NumberFormatException ignored) {
103                }
104            } else if ("Transfer-Encoding".equalsIgnoreCase(fieldName)) {
105                transferEncoding = value;
106            } else if ("User-Agent".equalsIgnoreCase(fieldName)) {
107                userAgent = value;
108            } else if ("Host".equalsIgnoreCase(fieldName)) {
109                host = value;
110            } else if ("Connection".equalsIgnoreCase(fieldName)) {
111                connection = value;
112            } else if ("Accept-Encoding".equalsIgnoreCase(fieldName)) {
113                acceptEncoding = value;
114            } else if ("Content-Type".equalsIgnoreCase(fieldName)) {
115                contentType = value;
116            } else if ("Proxy-Authorization".equalsIgnoreCase(fieldName)) {
117                proxyAuthorization = value;
118            }
119        }
120    }
121
122    public boolean isChunked() {
123        return "chunked".equalsIgnoreCase(transferEncoding);
124    }
125
126    public boolean hasConnectionClose() {
127        return "close".equalsIgnoreCase(connection);
128    }
129
130    public URI getUri() {
131        return uri;
132    }
133
134    public RawHeaders getHeaders() {
135        return headers;
136    }
137
138    public boolean isNoCache() {
139        return noCache;
140    }
141
142    public int getMaxAgeSeconds() {
143        return maxAgeSeconds;
144    }
145
146    public int getMaxStaleSeconds() {
147        return maxStaleSeconds;
148    }
149
150    public int getMinFreshSeconds() {
151        return minFreshSeconds;
152    }
153
154    public boolean isOnlyIfCached() {
155        return onlyIfCached;
156    }
157
158    public boolean hasAuthorization() {
159        return hasAuthorization;
160    }
161
162    public int getContentLength() {
163        return contentLength;
164    }
165
166    public String getTransferEncoding() {
167        return transferEncoding;
168    }
169
170    public String getUserAgent() {
171        return userAgent;
172    }
173
174    public String getHost() {
175        return host;
176    }
177
178    public String getConnection() {
179        return connection;
180    }
181
182    public String getAcceptEncoding() {
183        return acceptEncoding;
184    }
185
186    public String getContentType() {
187        return contentType;
188    }
189
190    public String getIfModifiedSince() {
191        return ifModifiedSince;
192    }
193
194    public String getIfNoneMatch() {
195        return ifNoneMatch;
196    }
197
198    public String getProxyAuthorization() {
199        return proxyAuthorization;
200    }
201
202    public void setChunked() {
203        if (this.transferEncoding != null) {
204            headers.removeAll("Transfer-Encoding");
205        }
206        headers.add("Transfer-Encoding", "chunked");
207        this.transferEncoding = "chunked";
208    }
209
210    public void setContentLength(int contentLength) {
211        if (this.contentLength != -1) {
212            headers.removeAll("Content-Length");
213        }
214        headers.add("Content-Length", Integer.toString(contentLength));
215        this.contentLength = contentLength;
216    }
217
218    public void setUserAgent(String userAgent) {
219        if (this.userAgent != null) {
220            headers.removeAll("User-Agent");
221        }
222        headers.add("User-Agent", userAgent);
223        this.userAgent = userAgent;
224    }
225
226    public void setHost(String host) {
227        if (this.host != null) {
228            headers.removeAll("Host");
229        }
230        headers.add("Host", host);
231        this.host = host;
232    }
233
234    public void setConnection(String connection) {
235        if (this.connection != null) {
236            headers.removeAll("Connection");
237        }
238        headers.add("Connection", connection);
239        this.connection = connection;
240    }
241
242    public void setAcceptEncoding(String acceptEncoding) {
243        if (this.acceptEncoding != null) {
244            headers.removeAll("Accept-Encoding");
245        }
246        headers.add("Accept-Encoding", acceptEncoding);
247        this.acceptEncoding = acceptEncoding;
248    }
249
250    public void setContentType(String contentType) {
251        if (this.contentType != null) {
252            headers.removeAll("Content-Type");
253        }
254        headers.add("Content-Type", contentType);
255        this.contentType = contentType;
256    }
257
258    public void setIfModifiedSince(Date date) {
259        if (ifModifiedSince != null) {
260            headers.removeAll("If-Modified-Since");
261        }
262        String formattedDate = HttpDate.format(date);
263        headers.add("If-Modified-Since", formattedDate);
264        ifModifiedSince = formattedDate;
265    }
266
267    public void setIfNoneMatch(String ifNoneMatch) {
268        if (this.ifNoneMatch != null) {
269            headers.removeAll("If-None-Match");
270        }
271        headers.add("If-None-Match", ifNoneMatch);
272        this.ifNoneMatch = ifNoneMatch;
273    }
274
275    /**
276     * Returns true if the request contains conditions that save the server from
277     * sending a response that the client has locally. When the caller adds
278     * conditions, this cache won't participate in the request.
279     */
280    public boolean hasConditions() {
281        return ifModifiedSince != null || ifNoneMatch != null;
282    }
283
284    public void addCookies(Map<String, List<String>> allCookieHeaders) {
285        for (Map.Entry<String, List<String>> entry : allCookieHeaders.entrySet()) {
286            String key = entry.getKey();
287            if ("Cookie".equalsIgnoreCase(key) || "Cookie2".equalsIgnoreCase(key)) {
288                headers.addAll(key, entry.getValue());
289            }
290        }
291    }
292}
293