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 * {@hide} 34 */ 35 public 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