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 'scan.dart'; |
| 12 import 'utils.dart'; |
| 13 |
| 14 /// A single challenge in a WWW-Authenticate header, parsed as per [RFC 2617][]. |
| 15 /// |
| 16 /// [RFC 2617]: http://tools.ietf.org/html/rfc2617 |
| 17 /// |
| 18 /// Each WWW-Authenticate header contains one or more challenges, representing |
| 19 /// valid ways to authenticate with the server. |
| 20 class AuthenticationChallenge { |
| 21 /// The scheme describing the type of authentication that's required, for |
| 22 /// example "basic" or "digest". |
| 23 /// |
| 24 /// This is normalized to always be lower-case. |
| 25 final String scheme; |
| 26 |
| 27 /// The parameters describing how to authenticate. |
| 28 /// |
| 29 /// The semantics of these parameters are scheme-specific. |
| 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 return wrapFormatException("authentication header", header, () { |
| 38 var scanner = new StringScanner(header); |
| 39 scanner.scan(whitespace); |
| 40 var challenges = parseList(scanner, () { |
| 41 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 var params = {}; |
| 46 |
| 47 // Consume initial empty values. |
| 48 while (scanner.scan(",")) { |
| 49 scanner.scan(whitespace); |
| 50 } |
| 51 |
| 52 _scanAuthParam(scanner, params); |
| 53 |
| 54 var beforeComma = scanner.position; |
| 55 while (scanner.scan(",")) { |
| 56 scanner.scan(whitespace); |
| 57 |
| 58 // Empty elements are allowed, but excluded from the results. |
| 59 if (scanner.matches(",") || scanner.isDone) continue; |
| 60 |
| 61 scanner.expect(token, name: "a token"); |
| 62 var name = scanner.lastMatch[0].toLowerCase(); |
| 63 scanner.scan(whitespace); |
| 64 |
| 65 // If there's no "=", then this is another challenge rather than a |
| 66 // parameter for the current challenge. |
| 67 if (!scanner.scan('=')) { |
| 68 scanner.position = beforeComma; |
| 69 break; |
| 70 } |
| 71 |
| 72 scanner.scan(whitespace); |
| 73 |
| 74 if (scanner.scan(token)) { |
| 75 params[name] = scanner.lastMatch[0]; |
| 76 } else { |
| 77 params[name] = expectQuotedString( |
| 78 scanner, name: "a token or a quoted string"); |
| 79 } |
| 80 |
| 81 scanner.scan(whitespace); |
| 82 beforeComma = scanner.position; |
| 83 } |
| 84 |
| 85 return new AuthenticationChallenge(scheme, params); |
| 86 }); |
| 87 |
| 88 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 return wrapFormatException("authentication challenge", challenge, () { |
| 98 var scanner = new StringScanner(challenge); |
| 99 scanner.scan(whitespace); |
| 100 var scheme = _scanScheme(scanner); |
| 101 |
| 102 var params = {}; |
| 103 parseList(scanner, () => _scanAuthParam(scanner, params)); |
| 104 |
| 105 scanner.expectDone(); |
| 106 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 scanner.expect(token, name: "a token"); |
| 116 var scheme = scanner.lastMatch[0].toLowerCase(); |
| 117 |
| 118 scanner.scan(whitespace); |
| 119 |
| 120 // The spec specifically requires a space between the scheme and its |
| 121 // params. |
| 122 if (scanner.lastMatch == null || !scanner.lastMatch[0].contains(" ")) { |
| 123 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 scanner.expect(token, name: "a token"); |
| 132 var name = scanner.lastMatch[0].toLowerCase(); |
| 133 scanner.scan(whitespace); |
| 134 scanner.expect('='); |
| 135 scanner.scan(whitespace); |
| 136 |
| 137 if (scanner.scan(token)) { |
| 138 params[name] = scanner.lastMatch[0]; |
| 139 } else { |
| 140 params[name] = expectQuotedString( |
| 141 scanner, name: "a token or a quoted string"); |
| 142 } |
| 143 |
| 144 scanner.scan(whitespace); |
| 145 } |
| 146 |
| 147 /// Creates a new challenge value with [scheme] and [parameters]. |
| 148 AuthenticationChallenge(this.scheme, Map<String, String> parameters) |
| 149 : parameters = new UnmodifiableMapView(parameters); |
| 150 } |
OLD | NEW |