Home | History | Annotate | Download | only in http
      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 
    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