Line data Source code
1 : // Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file
2 : // for details. All rights reserved. Use of this source code is governed by a
3 : // BSD-style license that can be found in the LICENSE file.
4 :
5 : import 'dart:collection';
6 :
7 : import 'package:string_scanner/string_scanner.dart';
8 :
9 : import 'case_insensitive_map.dart';
10 : import 'scan.dart';
11 : import 'utils.dart';
12 :
13 : /// A single challenge in a WWW-Authenticate header, parsed as per [RFC 2617][].
14 : ///
15 : /// [RFC 2617]: http://tools.ietf.org/html/rfc2617
16 : ///
17 : /// Each WWW-Authenticate header contains one or more challenges, representing
18 : /// valid ways to authenticate with the server.
19 : class AuthenticationChallenge {
20 : /// The scheme describing the type of authentication that's required, for
21 : /// example "basic" or "digest".
22 : ///
23 : /// This is normalized to always be lower-case.
24 : final String scheme;
25 :
26 : /// The parameters describing how to authenticate.
27 : ///
28 : /// The semantics of these parameters are scheme-specific. The keys of this
29 : /// map are case-insensitive.
30 : final Map<String, String> parameters;
31 :
32 : /// Parses a WWW-Authenticate header, which should contain one or more
33 : /// challenges.
34 : ///
35 : /// Throws a [FormatException] if the header is invalid.
36 : static List<AuthenticationChallenge> parseHeader(String header) {
37 0 : return wrapFormatException("authentication header", header, () {
38 0 : var scanner = new StringScanner(header);
39 0 : scanner.scan(whitespace);
40 0 : var challenges = parseList(scanner, () {
41 0 : var scheme = _scanScheme(scanner, whitespaceName: '" " or "="');
42 :
43 : // Manually parse the inner list. We need to do some lookahead to
44 : // disambiguate between an auth param and another challenge.
45 0 : var params = <String, String>{};
46 :
47 : // Consume initial empty values.
48 0 : while (scanner.scan(",")) {
49 0 : scanner.scan(whitespace);
50 : }
51 :
52 0 : _scanAuthParam(scanner, params);
53 :
54 0 : var beforeComma = scanner.position;
55 0 : while (scanner.scan(",")) {
56 0 : scanner.scan(whitespace);
57 :
58 : // Empty elements are allowed, but excluded from the results.
59 0 : if (scanner.matches(",") || scanner.isDone) continue;
60 :
61 0 : scanner.expect(token, name: "a token");
62 0 : var name = scanner.lastMatch[0];
63 0 : scanner.scan(whitespace);
64 :
65 : // If there's no "=", then this is another challenge rather than a
66 : // parameter for the current challenge.
67 0 : if (!scanner.scan('=')) {
68 0 : scanner.position = beforeComma;
69 : break;
70 : }
71 :
72 0 : scanner.scan(whitespace);
73 :
74 0 : if (scanner.scan(token)) {
75 0 : params[name] = scanner.lastMatch[0];
76 : } else {
77 0 : params[name] = expectQuotedString(
78 : scanner, name: "a token or a quoted string");
79 : }
80 :
81 0 : scanner.scan(whitespace);
82 0 : beforeComma = scanner.position;
83 : }
84 :
85 0 : return new AuthenticationChallenge(scheme, params);
86 : });
87 :
88 0 : scanner.expectDone();
89 : return challenges;
90 : });
91 : }
92 :
93 : /// Parses a single WWW-Authenticate challenge value.
94 : ///
95 : /// Throws a [FormatException] if the challenge is invalid.
96 : factory AuthenticationChallenge.parse(String challenge) {
97 0 : return wrapFormatException("authentication challenge", challenge, () {
98 0 : var scanner = new StringScanner(challenge);
99 0 : scanner.scan(whitespace);
100 0 : var scheme = _scanScheme(scanner);
101 :
102 0 : var params = <String, String>{};
103 0 : parseList(scanner, () => _scanAuthParam(scanner, params));
104 :
105 0 : scanner.expectDone();
106 0 : return new AuthenticationChallenge(scheme, params);
107 : });
108 : }
109 :
110 : /// Scans a single scheme name and asserts that it's followed by a space.
111 : ///
112 : /// If [whitespaceName] is passed, it's used as the name for exceptions thrown
113 : /// due to invalid trailing whitespace.
114 : static String _scanScheme(StringScanner scanner, {String whitespaceName}) {
115 0 : scanner.expect(token, name: "a token");
116 0 : var scheme = scanner.lastMatch[0].toLowerCase();
117 :
118 0 : scanner.scan(whitespace);
119 :
120 : // The spec specifically requires a space between the scheme and its
121 : // params.
122 0 : if (scanner.lastMatch == null || !scanner.lastMatch[0].contains(" ")) {
123 0 : scanner.expect(" ", name: whitespaceName);
124 : }
125 :
126 : return scheme;
127 : }
128 :
129 : /// Scans a single authentication parameter and stores its result in [params].
130 : static void _scanAuthParam(StringScanner scanner, Map params) {
131 0 : scanner.expect(token, name: "a token");
132 0 : var name = scanner.lastMatch[0];
133 0 : scanner.scan(whitespace);
134 0 : scanner.expect('=');
135 0 : scanner.scan(whitespace);
136 :
137 0 : if (scanner.scan(token)) {
138 0 : params[name] = scanner.lastMatch[0];
139 : } else {
140 0 : params[name] = expectQuotedString(
141 : scanner, name: "a token or a quoted string");
142 : }
143 :
144 0 : scanner.scan(whitespace);
145 : }
146 :
147 : /// Creates a new challenge value with [scheme] and [parameters].
148 : AuthenticationChallenge(this.scheme, Map<String, String> parameters)
149 0 : : parameters = new UnmodifiableMapView(
150 0 : new CaseInsensitiveMap.from(parameters));
151 : }
|