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