Home | History | Annotate | Download | only in http
      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 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(Locale.ROOT);
    384                 return;
    385             }
    386 
    387             if (token.equalsIgnoreCase(ALGORITHM_TOKEN)) {
    388                 mAlgorithm = value.toLowerCase(Locale.ROOT);
    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