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