• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.net.http;
18 
19 import android.util.Log;
20 
21 import java.util.ArrayList;
22 
23 import org.apache.http.HeaderElement;
24 import org.apache.http.entity.ContentLengthStrategy;
25 import org.apache.http.message.BasicHeaderValueParser;
26 import org.apache.http.message.ParserCursor;
27 import org.apache.http.protocol.HTTP;
28 import org.apache.http.util.CharArrayBuffer;
29 
30 /**
31  * Manages received headers
32  */
33 public 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 
Headers()152     public Headers() {
153         transferEncoding = NO_TRANSFER_ENCODING;
154         contentLength = NO_CONTENT_LENGTH;
155         connectionType = NO_CONN_TYPE;
156     }
157 
parseHeader(CharArrayBuffer buffer)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 
getTransferEncoding()309     public long getTransferEncoding() {
310         return transferEncoding;
311     }
312 
getContentLength()313     public long getContentLength() {
314         return contentLength;
315     }
316 
getConnectionType()317     public int getConnectionType() {
318         return connectionType;
319     }
320 
getContentType()321     public String getContentType() {
322         return mHeaders[IDX_CONTENT_TYPE];
323     }
324 
getContentEncoding()325     public String getContentEncoding() {
326         return mHeaders[IDX_CONTENT_ENCODING];
327     }
328 
getLocation()329     public String getLocation() {
330         return mHeaders[IDX_LOCATION];
331     }
332 
getWwwAuthenticate()333     public String getWwwAuthenticate() {
334         return mHeaders[IDX_WWW_AUTHENTICATE];
335     }
336 
getProxyAuthenticate()337     public String getProxyAuthenticate() {
338         return mHeaders[IDX_PROXY_AUTHENTICATE];
339     }
340 
getContentDisposition()341     public String getContentDisposition() {
342         return mHeaders[IDX_CONTENT_DISPOSITION];
343     }
344 
getAcceptRanges()345     public String getAcceptRanges() {
346         return mHeaders[IDX_ACCEPT_RANGES];
347     }
348 
getExpires()349     public String getExpires() {
350         return mHeaders[IDX_EXPIRES];
351     }
352 
getCacheControl()353     public String getCacheControl() {
354         return mHeaders[IDX_CACHE_CONTROL];
355     }
356 
getLastModified()357     public String getLastModified() {
358         return mHeaders[IDX_LAST_MODIFIED];
359     }
360 
getEtag()361     public String getEtag() {
362         return mHeaders[IDX_ETAG];
363     }
364 
getSetCookie()365     public ArrayList<String> getSetCookie() {
366         return this.cookies;
367     }
368 
getPragma()369     public String getPragma() {
370         return mHeaders[IDX_PRAGMA];
371     }
372 
getRefresh()373     public String getRefresh() {
374         return mHeaders[IDX_REFRESH];
375     }
376 
getXPermittedCrossDomainPolicies()377     public String getXPermittedCrossDomainPolicies() {
378         return mHeaders[IDX_X_PERMITTED_CROSS_DOMAIN_POLICIES];
379     }
380 
setContentLength(long value)381     public void setContentLength(long value) {
382         this.contentLength = value;
383     }
384 
setContentType(String value)385     public void setContentType(String value) {
386         mHeaders[IDX_CONTENT_TYPE] = value;
387     }
388 
setContentEncoding(String value)389     public void setContentEncoding(String value) {
390         mHeaders[IDX_CONTENT_ENCODING] = value;
391     }
392 
setLocation(String value)393     public void setLocation(String value) {
394         mHeaders[IDX_LOCATION] = value;
395     }
396 
setWwwAuthenticate(String value)397     public void setWwwAuthenticate(String value) {
398         mHeaders[IDX_WWW_AUTHENTICATE] = value;
399     }
400 
setProxyAuthenticate(String value)401     public void setProxyAuthenticate(String value) {
402         mHeaders[IDX_PROXY_AUTHENTICATE] = value;
403     }
404 
setContentDisposition(String value)405     public void setContentDisposition(String value) {
406         mHeaders[IDX_CONTENT_DISPOSITION] = value;
407     }
408 
setAcceptRanges(String value)409     public void setAcceptRanges(String value) {
410         mHeaders[IDX_ACCEPT_RANGES] = value;
411     }
412 
setExpires(String value)413     public void setExpires(String value) {
414         mHeaders[IDX_EXPIRES] = value;
415     }
416 
setCacheControl(String value)417     public void setCacheControl(String value) {
418         mHeaders[IDX_CACHE_CONTROL] = value;
419     }
420 
setLastModified(String value)421     public void setLastModified(String value) {
422         mHeaders[IDX_LAST_MODIFIED] = value;
423     }
424 
setEtag(String value)425     public void setEtag(String value) {
426         mHeaders[IDX_ETAG] = value;
427     }
428 
setXPermittedCrossDomainPolicies(String value)429     public void setXPermittedCrossDomainPolicies(String value) {
430         mHeaders[IDX_X_PERMITTED_CROSS_DOMAIN_POLICIES] = value;
431     }
432 
433     public interface HeaderCallback {
header(String name, String value)434         public void header(String name, String value);
435     }
436 
437     /**
438      * Reports all non-null headers to the callback
439      */
getHeaders(HeaderCallback hcb)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 
setConnectionType(CharArrayBuffer buffer, int pos)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      */
containsIgnoreCaseTrimmed(CharArrayBuffer buffer, int beginIndex, final String str)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      */
setLowercaseIndexOf(CharArrayBuffer buffer, final int ch)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