1 /* 2 * Copyright (C) 2007 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 java.util.Locale; 20 21 /** 22 * HttpAuthHeader: a class to store HTTP authentication-header parameters. 23 * For more information, see: RFC 2617: HTTP Authentication. 24 * 25 * {@hide} 26 */ 27 public class HttpAuthHeader { 28 /** 29 * Possible HTTP-authentication header tokens to search for: 30 */ 31 public final static String BASIC_TOKEN = "Basic"; 32 public final static String DIGEST_TOKEN = "Digest"; 33 34 private final static String REALM_TOKEN = "realm"; 35 private final static String NONCE_TOKEN = "nonce"; 36 private final static String STALE_TOKEN = "stale"; 37 private final static String OPAQUE_TOKEN = "opaque"; 38 private final static String QOP_TOKEN = "qop"; 39 private final static String ALGORITHM_TOKEN = "algorithm"; 40 41 /** 42 * An authentication scheme. We currently support two different schemes: 43 * HttpAuthHeader.BASIC - basic, and 44 * HttpAuthHeader.DIGEST - digest (algorithm=MD5, QOP="auth"). 45 */ 46 private int mScheme; 47 48 public static final int UNKNOWN = 0; 49 public static final int BASIC = 1; 50 public static final int DIGEST = 2; 51 52 /** 53 * A flag, indicating that the previous request from the client was 54 * rejected because the nonce value was stale. If stale is TRUE 55 * (case-insensitive), the client may wish to simply retry the request 56 * with a new encrypted response, without reprompting the user for a 57 * new username and password. 58 */ 59 private boolean mStale; 60 61 /** 62 * A string to be displayed to users so they know which username and 63 * password to use. 64 */ 65 private String mRealm; 66 67 /** 68 * A server-specified data string which should be uniquely generated 69 * each time a 401 response is made. 70 */ 71 private String mNonce; 72 73 /** 74 * A string of data, specified by the server, which should be returned 75 * by the client unchanged in the Authorization header of subsequent 76 * requests with URIs in the same protection space. 77 */ 78 private String mOpaque; 79 80 /** 81 * This directive is optional, but is made so only for backward 82 * compatibility with RFC 2069 [6]; it SHOULD be used by all 83 * implementations compliant with this version of the Digest scheme. 84 * If present, it is a quoted string of one or more tokens indicating 85 * the "quality of protection" values supported by the server. The 86 * value "auth" indicates authentication; the value "auth-int" 87 * indicates authentication with integrity protection. 88 */ 89 private String mQop; 90 91 /** 92 * A string indicating a pair of algorithms used to produce the digest 93 * and a checksum. If this is not present it is assumed to be "MD5". 94 */ 95 private String mAlgorithm; 96 97 /** 98 * Is this authentication request a proxy authentication request? 99 */ 100 private boolean mIsProxy; 101 102 /** 103 * Username string we get from the user. 104 */ 105 private String mUsername; 106 107 /** 108 * Password string we get from the user. 109 */ 110 private String mPassword; 111 112 /** 113 * Creates a new HTTP-authentication header object from the 114 * input header string. 115 * The header string is assumed to contain parameters of at 116 * most one authentication-scheme (ensured by the caller). 117 */ 118 public HttpAuthHeader(String header) { 119 if (header != null) { 120 parseHeader(header); 121 } 122 } 123 124 /** 125 * @return True iff this is a proxy authentication header. 126 */ 127 public boolean isProxy() { 128 return mIsProxy; 129 } 130 131 /** 132 * Marks this header as a proxy authentication header. 133 */ 134 public void setProxy() { 135 mIsProxy = true; 136 } 137 138 /** 139 * @return The username string. 140 */ 141 public String getUsername() { 142 return mUsername; 143 } 144 145 /** 146 * Sets the username string. 147 */ 148 public void setUsername(String username) { 149 mUsername = username; 150 } 151 152 /** 153 * @return The password string. 154 */ 155 public String getPassword() { 156 return mPassword; 157 } 158 159 /** 160 * Sets the password string. 161 */ 162 public void setPassword(String password) { 163 mPassword = password; 164 } 165 166 /** 167 * @return True iff this is the BASIC-authentication request. 168 */ 169 public boolean isBasic () { 170 return mScheme == BASIC; 171 } 172 173 /** 174 * @return True iff this is the DIGEST-authentication request. 175 */ 176 public boolean isDigest() { 177 return mScheme == DIGEST; 178 } 179 180 /** 181 * @return The authentication scheme requested. We currently 182 * support two schemes: 183 * HttpAuthHeader.BASIC - basic, and 184 * HttpAuthHeader.DIGEST - digest (algorithm=MD5, QOP="auth"). 185 */ 186 public int getScheme() { 187 return mScheme; 188 } 189 190 /** 191 * @return True if indicating that the previous request from 192 * the client was rejected because the nonce value was stale. 193 */ 194 public boolean getStale() { 195 return mStale; 196 } 197 198 /** 199 * @return The realm value or null if there is none. 200 */ 201 public String getRealm() { 202 return mRealm; 203 } 204 205 /** 206 * @return The nonce value or null if there is none. 207 */ 208 public String getNonce() { 209 return mNonce; 210 } 211 212 /** 213 * @return The opaque value or null if there is none. 214 */ 215 public String getOpaque() { 216 return mOpaque; 217 } 218 219 /** 220 * @return The QOP ("quality-of_protection") value or null if 221 * there is none. The QOP value is always lower-case. 222 */ 223 public String getQop() { 224 return mQop; 225 } 226 227 /** 228 * @return The name of the algorithm used or null if there is 229 * none. By default, MD5 is used. 230 */ 231 public String getAlgorithm() { 232 return mAlgorithm; 233 } 234 235 /** 236 * @return True iff the authentication scheme requested by the 237 * server is supported; currently supported schemes: 238 * BASIC, 239 * DIGEST (only algorithm="md5", no qop or qop="auth). 240 */ 241 public boolean isSupportedScheme() { 242 // it is a good idea to enforce non-null realms! 243 if (mRealm != null) { 244 if (mScheme == BASIC) { 245 return true; 246 } else { 247 if (mScheme == DIGEST) { 248 return 249 mAlgorithm.equals("md5") && 250 (mQop == null || mQop.equals("auth")); 251 } 252 } 253 } 254 255 return false; 256 } 257 258 /** 259 * Parses the header scheme name and then scheme parameters if 260 * the scheme is supported. 261 */ 262 private void parseHeader(String header) { 263 if (HttpLog.LOGV) { 264 HttpLog.v("HttpAuthHeader.parseHeader(): header: " + header); 265 } 266 267 if (header != null) { 268 String parameters = parseScheme(header); 269 if (parameters != null) { 270 // if we have a supported scheme 271 if (mScheme != UNKNOWN) { 272 parseParameters(parameters); 273 } 274 } 275 } 276 } 277 278 /** 279 * Parses the authentication scheme name. If we have a Digest 280 * scheme, sets the algorithm value to the default of MD5. 281 * @return The authentication scheme parameters string to be 282 * parsed later (if the scheme is supported) or null if failed 283 * to parse the scheme (the header value is null?). 284 */ 285 private String parseScheme(String header) { 286 if (header != null) { 287 int i = header.indexOf(' '); 288 if (i >= 0) { 289 String scheme = header.substring(0, i).trim(); 290 if (scheme.equalsIgnoreCase(DIGEST_TOKEN)) { 291 mScheme = DIGEST; 292 293 // md5 is the default algorithm!!! 294 mAlgorithm = "md5"; 295 } else { 296 if (scheme.equalsIgnoreCase(BASIC_TOKEN)) { 297 mScheme = BASIC; 298 } 299 } 300 301 return header.substring(i + 1); 302 } 303 } 304 305 return null; 306 } 307 308 /** 309 * Parses a comma-separated list of authentification scheme 310 * parameters. 311 */ 312 private void parseParameters(String parameters) { 313 if (HttpLog.LOGV) { 314 HttpLog.v("HttpAuthHeader.parseParameters():" + 315 " parameters: " + parameters); 316 } 317 318 if (parameters != null) { 319 int i; 320 do { 321 i = parameters.indexOf(','); 322 if (i < 0) { 323 // have only one parameter 324 parseParameter(parameters); 325 } else { 326 parseParameter(parameters.substring(0, i)); 327 parameters = parameters.substring(i + 1); 328 } 329 } while (i >= 0); 330 } 331 } 332 333 /** 334 * Parses a single authentication scheme parameter. The parameter 335 * string is expected to follow the format: PARAMETER=VALUE. 336 */ 337 private void parseParameter(String parameter) { 338 if (parameter != null) { 339 // here, we are looking for the 1st occurence of '=' only!!! 340 int i = parameter.indexOf('='); 341 if (i >= 0) { 342 String token = parameter.substring(0, i).trim(); 343 String value = 344 trimDoubleQuotesIfAny(parameter.substring(i + 1).trim()); 345 346 if (HttpLog.LOGV) { 347 HttpLog.v("HttpAuthHeader.parseParameter():" + 348 " token: " + token + 349 " value: " + value); 350 } 351 352 if (token.equalsIgnoreCase(REALM_TOKEN)) { 353 mRealm = value; 354 } else { 355 if (mScheme == DIGEST) { 356 parseParameter(token, value); 357 } 358 } 359 } 360 } 361 } 362 363 /** 364 * If the token is a known parameter name, parses and initializes 365 * the token value. 366 */ 367 private void parseParameter(String token, String value) { 368 if (token != null && value != null) { 369 if (token.equalsIgnoreCase(NONCE_TOKEN)) { 370 mNonce = value; 371 return; 372 } 373 374 if (token.equalsIgnoreCase(STALE_TOKEN)) { 375 parseStale(value); 376 return; 377 } 378 379 if (token.equalsIgnoreCase(OPAQUE_TOKEN)) { 380 mOpaque = value; 381 return; 382 } 383 384 if (token.equalsIgnoreCase(QOP_TOKEN)) { 385 mQop = value.toLowerCase(Locale.ROOT); 386 return; 387 } 388 389 if (token.equalsIgnoreCase(ALGORITHM_TOKEN)) { 390 mAlgorithm = value.toLowerCase(Locale.ROOT); 391 return; 392 } 393 } 394 } 395 396 /** 397 * Parses and initializes the 'stale' paramer value. Any value 398 * different from case-insensitive "true" is considered "false". 399 */ 400 private void parseStale(String value) { 401 if (value != null) { 402 if (value.equalsIgnoreCase("true")) { 403 mStale = true; 404 } 405 } 406 } 407 408 /** 409 * Trims double-quotes around a parameter value if there are any. 410 * @return The string value without the outermost pair of double- 411 * quotes or null if the original value is null. 412 */ 413 static private String trimDoubleQuotesIfAny(String value) { 414 if (value != null) { 415 int len = value.length(); 416 if (len > 2 && 417 value.charAt(0) == '\"' && value.charAt(len - 1) == '\"') { 418 return value.substring(1, len - 1); 419 } 420 } 421 422 return value; 423 } 424 } 425