summaryrefslogtreecommitdiffstats
path: root/android/src/android/net/http/HttpAuthHeader.java
blob: 2a1ee980072179789f849b2b12f1d9275cc1a42f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package android.net.http;

import java.util.Locale;

/**
 * HttpAuthHeader: a class to store HTTP authentication-header parameters.
 * For more information, see: RFC 2617: HTTP Authentication.
 */
public class HttpAuthHeader {
    /**
     * Possible HTTP-authentication header tokens to search for:
     */
    public final static String BASIC_TOKEN = "Basic";
    public final static String DIGEST_TOKEN = "Digest";

    private final static String REALM_TOKEN = "realm";
    private final static String NONCE_TOKEN = "nonce";
    private final static String STALE_TOKEN = "stale";
    private final static String OPAQUE_TOKEN = "opaque";
    private final static String QOP_TOKEN = "qop";
    private final static String ALGORITHM_TOKEN = "algorithm";

    /**
     * An authentication scheme. We currently support two different schemes:
     * HttpAuthHeader.BASIC  - basic, and
     * HttpAuthHeader.DIGEST - digest (algorithm=MD5, QOP="auth").
     */
    private int mScheme;

    public static final int UNKNOWN = 0;
    public static final int BASIC = 1;
    public static final int DIGEST = 2;

    /**
     * A flag, indicating that the previous request from the client was
     * rejected because the nonce value was stale. If stale is TRUE
     * (case-insensitive), the client may wish to simply retry the request
     * with a new encrypted response, without reprompting the user for a
     * new username and password.
     */
    private boolean mStale;

    /**
     * A string to be displayed to users so they know which username and
     * password to use.
     */
    private String mRealm;

    /**
     * A server-specified data string which should be uniquely generated
     * each time a 401 response is made.
     */
    private String mNonce;

    /**
     * A string of data, specified by the server, which should be returned
     *  by the client unchanged in the Authorization header of subsequent
     * requests with URIs in the same protection space.
     */
    private String mOpaque;

    /**
     * This directive is optional, but is made so only for backward
     * compatibility with RFC 2069 [6]; it SHOULD be used by all
     * implementations compliant with this version of the Digest scheme.
     * If present, it is a quoted string of one or more tokens indicating
     * the "quality of protection" values supported by the server.  The
     * value "auth" indicates authentication; the value "auth-int"
     * indicates authentication with integrity protection.
     */
    private String mQop;

    /**
     * A string indicating a pair of algorithms used to produce the digest
     * and a checksum. If this is not present it is assumed to be "MD5".
     */
    private String mAlgorithm;

    /**
     * Is this authentication request a proxy authentication request?
     */
    private boolean mIsProxy;

    /**
     * Username string we get from the user.
     */
    private String mUsername;

    /**
     * Password string we get from the user.
     */
    private String mPassword;

    /**
     * Creates a new HTTP-authentication header object from the
     * input header string.
     * The header string is assumed to contain parameters of at
     * most one authentication-scheme (ensured by the caller).
     */
    public HttpAuthHeader(String header) {
        if (header != null) {
            parseHeader(header);
        }
    }

    /**
     * @return True iff this is a proxy authentication header.
     */
    public boolean isProxy() {
        return mIsProxy;
    }

    /**
     * Marks this header as a proxy authentication header.
     */
    public void setProxy() {
        mIsProxy = true;
    }

    /**
     * @return The username string.
     */
    public String getUsername() {
        return mUsername;
    }

    /**
     * Sets the username string.
     */
    public void setUsername(String username) {
        mUsername = username;
    }

    /**
     * @return The password string.
     */
    public String getPassword() {
        return mPassword;
    }

    /**
     * Sets the password string.
     */
    public void setPassword(String password) {
        mPassword = password;
    }

    /**
     * @return True iff this is the  BASIC-authentication request.
     */
    public boolean isBasic () {
        return mScheme == BASIC;
    }

    /**
     * @return True iff this is the DIGEST-authentication request.
     */
    public boolean isDigest() {
        return mScheme == DIGEST;
    }

    /**
     * @return The authentication scheme requested. We currently
     * support two schemes:
     * HttpAuthHeader.BASIC  - basic, and
     * HttpAuthHeader.DIGEST - digest (algorithm=MD5, QOP="auth").
     */
    public int getScheme() {
        return mScheme;
    }

    /**
     * @return True if indicating that the previous request from
     * the client was rejected because the nonce value was stale.
     */
    public boolean getStale() {
        return mStale;
    }

    /**
     * @return The realm value or null if there is none.
     */
    public String getRealm() {
        return mRealm;
    }

    /**
     * @return The nonce value or null if there is none.
     */
    public String getNonce() {
        return mNonce;
    }

    /**
     * @return The opaque value or null if there is none.
     */
    public String getOpaque() {
        return mOpaque;
    }

    /**
     * @return The QOP ("quality-of_protection") value or null if
     * there is none. The QOP value is always lower-case.
     */
    public String getQop() {
        return mQop;
    }

    /**
     * @return The name of the algorithm used or null if there is
     * none. By default, MD5 is used.
     */
    public String getAlgorithm() {
        return mAlgorithm;
    }

    /**
     * @return True iff the authentication scheme requested by the
     * server is supported; currently supported schemes:
     * BASIC,
     * DIGEST (only algorithm="md5", no qop or qop="auth).
     */
    public boolean isSupportedScheme() {
        // it is a good idea to enforce non-null realms!
        if (mRealm != null) {
            if (mScheme == BASIC) {
                return true;
            } else {
                if (mScheme == DIGEST) {
                    return
                        mAlgorithm.equals("md5") &&
                        (mQop == null || mQop.equals("auth"));
                }
            }
        }

        return false;
    }

    /**
     * Parses the header scheme name and then scheme parameters if
     * the scheme is supported.
     */
    private void parseHeader(String header) {
        if (HttpLog.LOGV) {
            HttpLog.v("HttpAuthHeader.parseHeader(): header: " + header);
        }

        if (header != null) {
            String parameters = parseScheme(header);
            if (parameters != null) {
                // if we have a supported scheme
                if (mScheme != UNKNOWN) {
                    parseParameters(parameters);
                }
            }
        }
    }

    /**
     * Parses the authentication scheme name. If we have a Digest
     * scheme, sets the algorithm value to the default of MD5.
     * @return The authentication scheme parameters string to be
     * parsed later (if the scheme is supported) or null if failed
     * to parse the scheme (the header value is null?).
     */
    private String parseScheme(String header) {
        if (header != null) {
            int i = header.indexOf(' ');
            if (i >= 0) {
                String scheme = header.substring(0, i).trim();
                if (scheme.equalsIgnoreCase(DIGEST_TOKEN)) {
                    mScheme = DIGEST;

                    // md5 is the default algorithm!!!
                    mAlgorithm = "md5";
                } else {
                    if (scheme.equalsIgnoreCase(BASIC_TOKEN)) {
                        mScheme = BASIC;
                    }
                }

                return header.substring(i + 1);
            }
        }

        return null;
    }

    /**
     * Parses a comma-separated list of authentification scheme
     * parameters.
     */
    private void parseParameters(String parameters) {
        if (HttpLog.LOGV) {
            HttpLog.v("HttpAuthHeader.parseParameters():" +
                      " parameters: " + parameters);
        }

        if (parameters != null) {
            int i;
            do {
                i = parameters.indexOf(',');
                if (i < 0) {
                    // have only one parameter
                    parseParameter(parameters);
                } else {
                    parseParameter(parameters.substring(0, i));
                    parameters = parameters.substring(i + 1);
                }
            } while (i >= 0);
        }
    }

    /**
     * Parses a single authentication scheme parameter. The parameter
     * string is expected to follow the format: PARAMETER=VALUE.
     */
    private void parseParameter(String parameter) {
        if (parameter != null) {
            // here, we are looking for the 1st occurence of '=' only!!!
            int i = parameter.indexOf('=');
            if (i >= 0) {
                String token = parameter.substring(0, i).trim();
                String value =
                    trimDoubleQuotesIfAny(parameter.substring(i + 1).trim());

                if (HttpLog.LOGV) {
                    HttpLog.v("HttpAuthHeader.parseParameter():" +
                              " token: " + token +
                              " value: " + value);
                }

                if (token.equalsIgnoreCase(REALM_TOKEN)) {
                    mRealm = value;
                } else {
                    if (mScheme == DIGEST) {
                        parseParameter(token, value);
                    }
                }
            }
        }
    }

    /**
     * If the token is a known parameter name, parses and initializes
     * the token value.
     */
    private void parseParameter(String token, String value) {
        if (token != null && value != null) {
            if (token.equalsIgnoreCase(NONCE_TOKEN)) {
                mNonce = value;
                return;
            }

            if (token.equalsIgnoreCase(STALE_TOKEN)) {
                parseStale(value);
                return;
            }

            if (token.equalsIgnoreCase(OPAQUE_TOKEN)) {
                mOpaque = value;
                return;
            }

            if (token.equalsIgnoreCase(QOP_TOKEN)) {
                mQop = value.toLowerCase(Locale.ROOT);
                return;
            }

            if (token.equalsIgnoreCase(ALGORITHM_TOKEN)) {
                mAlgorithm = value.toLowerCase(Locale.ROOT);
                return;
            }
        }
    }

    /**
     * Parses and initializes the 'stale' paramer value. Any value
     * different from case-insensitive "true" is considered "false".
     */
    private void parseStale(String value) {
        if (value != null) {
            if (value.equalsIgnoreCase("true")) {
                mStale = true;
            }
        }
    }

    /**
     * Trims double-quotes around a parameter value if there are any.
     * @return The string value without the outermost pair of double-
     * quotes or null if the original value is null.
     */
    static private String trimDoubleQuotesIfAny(String value) {
        if (value != null) {
            int len = value.length();
            if (len > 2 &&
                value.charAt(0) == '\"' && value.charAt(len - 1) == '\"') {
                return value.substring(1, len - 1);
            }
        }

        return value;
    }
}