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