1/*
2 * Copyright (C) 2006 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 android.net.http;
18
19import android.util.Log;
20
21import java.util.ArrayList;
22
23import org.apache.http.HeaderElement;
24import org.apache.http.entity.ContentLengthStrategy;
25import org.apache.http.message.BasicHeaderValueParser;
26import org.apache.http.message.ParserCursor;
27import org.apache.http.protocol.HTTP;
28import org.apache.http.util.CharArrayBuffer;
29
30/**
31 * Manages received headers
32 *
33 * {@hide}
34 */
35public final class Headers {
36    private static final String LOGTAG = "Http";
37
38    // header parsing constant
39    /**
40     * indicate HTTP 1.0 connection close after the response
41     */
42    public final static int CONN_CLOSE = 1;
43    /**
44     * indicate HTTP 1.1 connection keep alive
45     */
46    public final static int CONN_KEEP_ALIVE = 2;
47
48    // initial values.
49    public final static int NO_CONN_TYPE = 0;
50    public final static long NO_TRANSFER_ENCODING = 0;
51    public final static long NO_CONTENT_LENGTH = -1;
52
53    // header strings
54    public final static String TRANSFER_ENCODING = "transfer-encoding";
55    public final static String CONTENT_LEN = "content-length";
56    public final static String CONTENT_TYPE = "content-type";
57    public final static String CONTENT_ENCODING = "content-encoding";
58    public final static String CONN_DIRECTIVE = "connection";
59
60    public final static String LOCATION = "location";
61    public final static String PROXY_CONNECTION = "proxy-connection";
62
63    public final static String WWW_AUTHENTICATE = "www-authenticate";
64    public final static String PROXY_AUTHENTICATE = "proxy-authenticate";
65    public final static String CONTENT_DISPOSITION = "content-disposition";
66    public final static String ACCEPT_RANGES = "accept-ranges";
67    public final static String EXPIRES = "expires";
68    public final static String CACHE_CONTROL = "cache-control";
69    public final static String LAST_MODIFIED = "last-modified";
70    public final static String ETAG = "etag";
71    public final static String SET_COOKIE = "set-cookie";
72    public final static String PRAGMA = "pragma";
73    public final static String REFRESH = "refresh";
74    public final static String X_PERMITTED_CROSS_DOMAIN_POLICIES = "x-permitted-cross-domain-policies";
75
76    // following hash are generated by String.hashCode()
77    private final static int HASH_TRANSFER_ENCODING = 1274458357;
78    private final static int HASH_CONTENT_LEN = -1132779846;
79    private final static int HASH_CONTENT_TYPE = 785670158;
80    private final static int HASH_CONTENT_ENCODING = 2095084583;
81    private final static int HASH_CONN_DIRECTIVE = -775651618;
82    private final static int HASH_LOCATION = 1901043637;
83    private final static int HASH_PROXY_CONNECTION = 285929373;
84    private final static int HASH_WWW_AUTHENTICATE = -243037365;
85    private final static int HASH_PROXY_AUTHENTICATE = -301767724;
86    private final static int HASH_CONTENT_DISPOSITION = -1267267485;
87    private final static int HASH_ACCEPT_RANGES = 1397189435;
88    private final static int HASH_EXPIRES = -1309235404;
89    private final static int HASH_CACHE_CONTROL = -208775662;
90    private final static int HASH_LAST_MODIFIED = 150043680;
91    private final static int HASH_ETAG = 3123477;
92    private final static int HASH_SET_COOKIE = 1237214767;
93    private final static int HASH_PRAGMA = -980228804;
94    private final static int HASH_REFRESH = 1085444827;
95    private final static int HASH_X_PERMITTED_CROSS_DOMAIN_POLICIES = -1345594014;
96
97    // keep any headers that require direct access in a presized
98    // string array
99    private final static int IDX_TRANSFER_ENCODING = 0;
100    private final static int IDX_CONTENT_LEN = 1;
101    private final static int IDX_CONTENT_TYPE = 2;
102    private final static int IDX_CONTENT_ENCODING = 3;
103    private final static int IDX_CONN_DIRECTIVE = 4;
104    private final static int IDX_LOCATION = 5;
105    private final static int IDX_PROXY_CONNECTION = 6;
106    private final static int IDX_WWW_AUTHENTICATE = 7;
107    private final static int IDX_PROXY_AUTHENTICATE = 8;
108    private final static int IDX_CONTENT_DISPOSITION = 9;
109    private final static int IDX_ACCEPT_RANGES = 10;
110    private final static int IDX_EXPIRES = 11;
111    private final static int IDX_CACHE_CONTROL = 12;
112    private final static int IDX_LAST_MODIFIED = 13;
113    private final static int IDX_ETAG = 14;
114    private final static int IDX_SET_COOKIE = 15;
115    private final static int IDX_PRAGMA = 16;
116    private final static int IDX_REFRESH = 17;
117    private final static int IDX_X_PERMITTED_CROSS_DOMAIN_POLICIES = 18;
118
119    private final static int HEADER_COUNT = 19;
120
121    /* parsed values */
122    private long transferEncoding;
123    private long contentLength; // Content length of the incoming data
124    private int connectionType;
125    private ArrayList<String> cookies = new ArrayList<String>(2);
126
127    private String[] mHeaders = new String[HEADER_COUNT];
128    private final static String[] sHeaderNames = {
129        TRANSFER_ENCODING,
130        CONTENT_LEN,
131        CONTENT_TYPE,
132        CONTENT_ENCODING,
133        CONN_DIRECTIVE,
134        LOCATION,
135        PROXY_CONNECTION,
136        WWW_AUTHENTICATE,
137        PROXY_AUTHENTICATE,
138        CONTENT_DISPOSITION,
139        ACCEPT_RANGES,
140        EXPIRES,
141        CACHE_CONTROL,
142        LAST_MODIFIED,
143        ETAG,
144        SET_COOKIE,
145        PRAGMA,
146        REFRESH,
147        X_PERMITTED_CROSS_DOMAIN_POLICIES
148    };
149
150    // Catch-all for headers not explicitly handled
151    private ArrayList<String> mExtraHeaderNames = new ArrayList<String>(4);
152    private ArrayList<String> mExtraHeaderValues = new ArrayList<String>(4);
153
154    public Headers() {
155        transferEncoding = NO_TRANSFER_ENCODING;
156        contentLength = NO_CONTENT_LENGTH;
157        connectionType = NO_CONN_TYPE;
158    }
159
160    public void parseHeader(CharArrayBuffer buffer) {
161        int pos = CharArrayBuffers.setLowercaseIndexOf(buffer, ':');
162        if (pos == -1) {
163            return;
164        }
165        String name = buffer.substringTrimmed(0, pos);
166        if (name.length() == 0) {
167            return;
168        }
169        pos++;
170
171        String val = buffer.substringTrimmed(pos, buffer.length());
172        if (HttpLog.LOGV) {
173            HttpLog.v("hdr " + buffer.length() + " " + buffer);
174        }
175
176        switch (name.hashCode()) {
177        case HASH_TRANSFER_ENCODING:
178            if (name.equals(TRANSFER_ENCODING)) {
179                mHeaders[IDX_TRANSFER_ENCODING] = val;
180                HeaderElement[] encodings = BasicHeaderValueParser.DEFAULT
181                        .parseElements(buffer, new ParserCursor(pos,
182                                buffer.length()));
183                // The chunked encoding must be the last one applied RFC2616,
184                // 14.41
185                int len = encodings.length;
186                if (HTTP.IDENTITY_CODING.equalsIgnoreCase(val)) {
187                    transferEncoding = ContentLengthStrategy.IDENTITY;
188                } else if ((len > 0)
189                        && (HTTP.CHUNK_CODING
190                                .equalsIgnoreCase(encodings[len - 1].getName()))) {
191                    transferEncoding = ContentLengthStrategy.CHUNKED;
192                } else {
193                    transferEncoding = ContentLengthStrategy.IDENTITY;
194                }
195            }
196            break;
197        case HASH_CONTENT_LEN:
198            if (name.equals(CONTENT_LEN)) {
199                mHeaders[IDX_CONTENT_LEN] = val;
200                try {
201                    contentLength = Long.parseLong(val);
202                } catch (NumberFormatException e) {
203                    if (false) {
204                        Log.v(LOGTAG, "Headers.headers(): error parsing"
205                                + " content length: " + buffer.toString());
206                    }
207                }
208            }
209            break;
210        case HASH_CONTENT_TYPE:
211            if (name.equals(CONTENT_TYPE)) {
212                mHeaders[IDX_CONTENT_TYPE] = val;
213            }
214            break;
215        case HASH_CONTENT_ENCODING:
216            if (name.equals(CONTENT_ENCODING)) {
217                mHeaders[IDX_CONTENT_ENCODING] = val;
218            }
219            break;
220        case HASH_CONN_DIRECTIVE:
221            if (name.equals(CONN_DIRECTIVE)) {
222                mHeaders[IDX_CONN_DIRECTIVE] = val;
223                setConnectionType(buffer, pos);
224            }
225            break;
226        case HASH_LOCATION:
227            if (name.equals(LOCATION)) {
228                mHeaders[IDX_LOCATION] = val;
229            }
230            break;
231        case HASH_PROXY_CONNECTION:
232            if (name.equals(PROXY_CONNECTION)) {
233                mHeaders[IDX_PROXY_CONNECTION] = val;
234                setConnectionType(buffer, pos);
235            }
236            break;
237        case HASH_WWW_AUTHENTICATE:
238            if (name.equals(WWW_AUTHENTICATE)) {
239                mHeaders[IDX_WWW_AUTHENTICATE] = val;
240            }
241            break;
242        case HASH_PROXY_AUTHENTICATE:
243            if (name.equals(PROXY_AUTHENTICATE)) {
244                mHeaders[IDX_PROXY_AUTHENTICATE] = val;
245            }
246            break;
247        case HASH_CONTENT_DISPOSITION:
248            if (name.equals(CONTENT_DISPOSITION)) {
249                mHeaders[IDX_CONTENT_DISPOSITION] = val;
250            }
251            break;
252        case HASH_ACCEPT_RANGES:
253            if (name.equals(ACCEPT_RANGES)) {
254                mHeaders[IDX_ACCEPT_RANGES] = val;
255            }
256            break;
257        case HASH_EXPIRES:
258            if (name.equals(EXPIRES)) {
259                mHeaders[IDX_EXPIRES] = val;
260            }
261            break;
262        case HASH_CACHE_CONTROL:
263            if (name.equals(CACHE_CONTROL)) {
264                // In case where we receive more than one header, create a ',' separated list.
265                // This should be ok, according to RFC 2616 chapter 4.2
266                if (mHeaders[IDX_CACHE_CONTROL] != null &&
267                    mHeaders[IDX_CACHE_CONTROL].length() > 0) {
268                    mHeaders[IDX_CACHE_CONTROL] += (',' + val);
269                } else {
270                    mHeaders[IDX_CACHE_CONTROL] = val;
271                }
272            }
273            break;
274        case HASH_LAST_MODIFIED:
275            if (name.equals(LAST_MODIFIED)) {
276                mHeaders[IDX_LAST_MODIFIED] = val;
277            }
278            break;
279        case HASH_ETAG:
280            if (name.equals(ETAG)) {
281                mHeaders[IDX_ETAG] = val;
282            }
283            break;
284        case HASH_SET_COOKIE:
285            if (name.equals(SET_COOKIE)) {
286                mHeaders[IDX_SET_COOKIE] = val;
287                cookies.add(val);
288            }
289            break;
290        case HASH_PRAGMA:
291            if (name.equals(PRAGMA)) {
292                mHeaders[IDX_PRAGMA] = val;
293            }
294            break;
295        case HASH_REFRESH:
296            if (name.equals(REFRESH)) {
297                mHeaders[IDX_REFRESH] = val;
298            }
299            break;
300        case HASH_X_PERMITTED_CROSS_DOMAIN_POLICIES:
301            if (name.equals(X_PERMITTED_CROSS_DOMAIN_POLICIES)) {
302                mHeaders[IDX_X_PERMITTED_CROSS_DOMAIN_POLICIES] = val;
303            }
304            break;
305        default:
306            mExtraHeaderNames.add(name);
307            mExtraHeaderValues.add(val);
308        }
309    }
310
311    public long getTransferEncoding() {
312        return transferEncoding;
313    }
314
315    public long getContentLength() {
316        return contentLength;
317    }
318
319    public int getConnectionType() {
320        return connectionType;
321    }
322
323    public String getContentType() {
324        return mHeaders[IDX_CONTENT_TYPE];
325    }
326
327    public String getContentEncoding() {
328        return mHeaders[IDX_CONTENT_ENCODING];
329    }
330
331    public String getLocation() {
332        return mHeaders[IDX_LOCATION];
333    }
334
335    public String getWwwAuthenticate() {
336        return mHeaders[IDX_WWW_AUTHENTICATE];
337    }
338
339    public String getProxyAuthenticate() {
340        return mHeaders[IDX_PROXY_AUTHENTICATE];
341    }
342
343    public String getContentDisposition() {
344        return mHeaders[IDX_CONTENT_DISPOSITION];
345    }
346
347    public String getAcceptRanges() {
348        return mHeaders[IDX_ACCEPT_RANGES];
349    }
350
351    public String getExpires() {
352        return mHeaders[IDX_EXPIRES];
353    }
354
355    public String getCacheControl() {
356        return mHeaders[IDX_CACHE_CONTROL];
357    }
358
359    public String getLastModified() {
360        return mHeaders[IDX_LAST_MODIFIED];
361    }
362
363    public String getEtag() {
364        return mHeaders[IDX_ETAG];
365    }
366
367    public ArrayList<String> getSetCookie() {
368        return this.cookies;
369    }
370
371    public String getPragma() {
372        return mHeaders[IDX_PRAGMA];
373    }
374
375    public String getRefresh() {
376        return mHeaders[IDX_REFRESH];
377    }
378
379    public String getXPermittedCrossDomainPolicies() {
380        return mHeaders[IDX_X_PERMITTED_CROSS_DOMAIN_POLICIES];
381    }
382
383    public void setContentLength(long value) {
384        this.contentLength = value;
385    }
386
387    public void setContentType(String value) {
388        mHeaders[IDX_CONTENT_TYPE] = value;
389    }
390
391    public void setContentEncoding(String value) {
392        mHeaders[IDX_CONTENT_ENCODING] = value;
393    }
394
395    public void setLocation(String value) {
396        mHeaders[IDX_LOCATION] = value;
397    }
398
399    public void setWwwAuthenticate(String value) {
400        mHeaders[IDX_WWW_AUTHENTICATE] = value;
401    }
402
403    public void setProxyAuthenticate(String value) {
404        mHeaders[IDX_PROXY_AUTHENTICATE] = value;
405    }
406
407    public void setContentDisposition(String value) {
408        mHeaders[IDX_CONTENT_DISPOSITION] = value;
409    }
410
411    public void setAcceptRanges(String value) {
412        mHeaders[IDX_ACCEPT_RANGES] = value;
413    }
414
415    public void setExpires(String value) {
416        mHeaders[IDX_EXPIRES] = value;
417    }
418
419    public void setCacheControl(String value) {
420        mHeaders[IDX_CACHE_CONTROL] = value;
421    }
422
423    public void setLastModified(String value) {
424        mHeaders[IDX_LAST_MODIFIED] = value;
425    }
426
427    public void setEtag(String value) {
428        mHeaders[IDX_ETAG] = value;
429    }
430
431    public void setXPermittedCrossDomainPolicies(String value) {
432        mHeaders[IDX_X_PERMITTED_CROSS_DOMAIN_POLICIES] = value;
433    }
434
435    public interface HeaderCallback {
436        public void header(String name, String value);
437    }
438
439    /**
440     * Reports all non-null headers to the callback
441     */
442    public void getHeaders(HeaderCallback hcb) {
443        for (int i = 0; i < HEADER_COUNT; i++) {
444            String h = mHeaders[i];
445            if (h != null) {
446                hcb.header(sHeaderNames[i], h);
447            }
448        }
449        int extraLen = mExtraHeaderNames.size();
450        for (int i = 0; i < extraLen; i++) {
451            if (false) {
452                HttpLog.v("Headers.getHeaders() extra: " + i + " " +
453                          mExtraHeaderNames.get(i) + " " + mExtraHeaderValues.get(i));
454            }
455            hcb.header(mExtraHeaderNames.get(i),
456                       mExtraHeaderValues.get(i));
457        }
458
459    }
460
461    private void setConnectionType(CharArrayBuffer buffer, int pos) {
462        if (CharArrayBuffers.containsIgnoreCaseTrimmed(
463                buffer, pos, HTTP.CONN_CLOSE)) {
464            connectionType = CONN_CLOSE;
465        } else if (CharArrayBuffers.containsIgnoreCaseTrimmed(
466                buffer, pos, HTTP.CONN_KEEP_ALIVE)) {
467            connectionType = CONN_KEEP_ALIVE;
468        }
469    }
470}
471