1 /* 2 * Copyright (C) 2011 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 libcore.net.http; 18 19 import java.util.ArrayList; 20 import java.util.List; 21 22 /** 23 * @hide 24 */ 25 public final class HeaderParser { 26 27 public interface CacheControlHandler { handle(String directive, String parameter)28 void handle(String directive, String parameter); 29 } 30 31 /** 32 * Parse a comma-separated list of cache control header values. 33 */ parseCacheControl(String value, CacheControlHandler handler)34 public static void parseCacheControl(String value, CacheControlHandler handler) { 35 int pos = 0; 36 while (pos < value.length()) { 37 int tokenStart = pos; 38 pos = skipUntil(value, pos, "=,"); 39 String directive = value.substring(tokenStart, pos).trim(); 40 41 if (pos == value.length() || value.charAt(pos) == ',') { 42 pos++; // consume ',' (if necessary) 43 handler.handle(directive, null); 44 continue; 45 } 46 47 pos++; // consume '=' 48 pos = skipWhitespace(value, pos); 49 50 String parameter; 51 52 // quoted string 53 if (pos < value.length() && value.charAt(pos) == '\"') { 54 pos++; // consume '"' open quote 55 int parameterStart = pos; 56 pos = skipUntil(value, pos, "\""); 57 parameter = value.substring(parameterStart, pos); 58 pos++; // consume '"' close quote (if necessary) 59 60 // unquoted string 61 } else { 62 int parameterStart = pos; 63 pos = skipUntil(value, pos, ","); 64 parameter = value.substring(parameterStart, pos).trim(); 65 } 66 67 handler.handle(directive, parameter); 68 } 69 } 70 71 /** 72 * Parse RFC 2617 challenges. This API is only interested in the scheme 73 * name and realm. 74 */ parseChallenges( RawHeaders responseHeaders, String challengeHeader)75 public static List<Challenge> parseChallenges( 76 RawHeaders responseHeaders, String challengeHeader) { 77 /* 78 * auth-scheme = token 79 * auth-param = token "=" ( token | quoted-string ) 80 * challenge = auth-scheme 1*SP 1#auth-param 81 * realm = "realm" "=" realm-value 82 * realm-value = quoted-string 83 */ 84 List<Challenge> result = new ArrayList<Challenge>(); 85 for (int h = 0; h < responseHeaders.length(); h++) { 86 if (!challengeHeader.equalsIgnoreCase(responseHeaders.getFieldName(h))) { 87 continue; 88 } 89 String value = responseHeaders.getValue(h); 90 int pos = 0; 91 while (pos < value.length()) { 92 int tokenStart = pos; 93 pos = skipUntil(value, pos, " "); 94 95 String scheme = value.substring(tokenStart, pos).trim(); 96 pos = skipWhitespace(value, pos); 97 98 // TODO: This currently only handles schemes with a 'realm' parameter; 99 // It needs to be fixed to handle any scheme and any parameters 100 // http://code.google.com/p/android/issues/detail?id=11140 101 102 if (!value.regionMatches(pos, "realm=\"", 0, "realm=\"".length())) { 103 break; // unexpected challenge parameter; give up 104 } 105 106 pos += "realm=\"".length(); 107 int realmStart = pos; 108 pos = skipUntil(value, pos, "\""); 109 String realm = value.substring(realmStart, pos); 110 pos++; // consume '"' close quote 111 pos = skipUntil(value, pos, ","); 112 pos++; // consume ',' comma 113 pos = skipWhitespace(value, pos); 114 result.add(new Challenge(scheme, realm)); 115 } 116 } 117 return result; 118 } 119 120 /** 121 * Returns the next index in {@code input} at or after {@code pos} that 122 * contains a character from {@code characters}. Returns the input length if 123 * none of the requested characters can be found. 124 */ skipUntil(String input, int pos, String characters)125 private static int skipUntil(String input, int pos, String characters) { 126 for (; pos < input.length(); pos++) { 127 if (characters.indexOf(input.charAt(pos)) != -1) { 128 break; 129 } 130 } 131 return pos; 132 } 133 134 /** 135 * Returns the next non-whitespace character in {@code input} that is white 136 * space. Result is undefined if input contains newline characters. 137 */ skipWhitespace(String input, int pos)138 private static int skipWhitespace(String input, int pos) { 139 for (; pos < input.length(); pos++) { 140 char c = input.charAt(pos); 141 if (c != ' ' && c != '\t') { 142 break; 143 } 144 } 145 return pos; 146 } 147 148 /** 149 * Returns {@code value} as a positive integer, or 0 if it is negative, or 150 * -1 if it cannot be parsed. 151 */ parseSeconds(String value)152 public static int parseSeconds(String value) { 153 try { 154 long seconds = Long.parseLong(value); 155 if (seconds > Integer.MAX_VALUE) { 156 return Integer.MAX_VALUE; 157 } else if (seconds < 0) { 158 return 0; 159 } else { 160 return (int) seconds; 161 } 162 } catch (NumberFormatException e) { 163 return -1; 164 } 165 } 166 } 167