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