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  * {@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