cr
diff --git a/lib/src/authentication_challenge.dart b/lib/src/authentication_challenge.dart
index 22dc2a6..773ee3b 100644
--- a/lib/src/authentication_challenge.dart
+++ b/lib/src/authentication_challenge.dart
@@ -40,20 +40,17 @@
var scanner = new StringScanner(header);
scanner.scan(whitespace);
var challenges = parseList(scanner, () {
- scanner.expect(token, name: "a token");
- var scheme = scanner.lastMatch[0].toLowerCase();
-
- scanner.scan(whitespace);
-
- // The spec specifically requires a space between the scheme and its
- // params.
- if (scanner.lastMatch == null || !scanner.lastMatch[0].contains(" ")) {
- scanner.expect(" ", name: '" " or "="');
- }
+ var scheme = _scanScheme(scanner, whitespaceName: '" " or "="');
// Manually parse the inner list. We need to do some lookahead to
// disambiguate between an auth param and another challenge.
var params = {};
+
+ // Consume initial empty values.
+ while (scanner.scan(",")) {
+ scanner.scan(whitespace);
+ }
+
_scanAuthParam(scanner, params);
var beforeComma = scanner.position;
@@ -61,7 +58,7 @@
scanner.scan(whitespace);
// Empty elements are allowed, but excluded from the results.
- if (scanner.matches(",")) continue;
+ if (scanner.matches(",") || scanner.isDone) continue;
scanner.expect(token, name: "a token");
var name = scanner.lastMatch[0];
@@ -102,16 +99,7 @@
return wrapFormatException("authentication challenge", challenge, () {
var scanner = new StringScanner(challenge);
scanner.scan(whitespace);
- scanner.expect(token, name: "a token");
- var scheme = scanner.lastMatch[0].toLowerCase();
-
- scanner.scan(whitespace);
-
- // The spec specifically requires a space between the scheme and its
- // params.
- if (scanner.lastMatch == null || !scanner.lastMatch[0].contains(" ")) {
- scanner.expect(" ");
- }
+ var scheme = _scanScheme(scanner);
var params = {};
parseList(scanner, () => _scanAuthParam(scanner, params));
@@ -121,6 +109,25 @@
});
}
+ /// Scans a single scheme name and asserts that it's followed by a space.
+ ///
+ /// If [whitespaceName] is passed, it's used as the name for exceptions thrown
+ /// due to invalid trailing whitespace.
+ static String _scanScheme(StringScanner scanner, {String whitespaceName}) {
+ scanner.expect(token, name: "a token");
+ var scheme = scanner.lastMatch[0].toLowerCase();
+
+ scanner.scan(whitespace);
+
+ // The spec specifically requires a space between the scheme and its
+ // params.
+ if (scanner.lastMatch == null || !scanner.lastMatch[0].contains(" ")) {
+ scanner.expect(" ", name: whitespaceName);
+ }
+
+ return scheme;
+ }
+
/// Scans a single authentication parameter and stores its result in [params].
static void _scanAuthParam(StringScanner scanner, Map params) {
scanner.expect(token, name: "a token");
@@ -139,9 +146,7 @@
scanner.scan(whitespace);
}
- /// Creates a new challenge value with [scheme] and, optionally, [parameters].
- ///
- /// If [parameters] isn't passed, it defaults to an empty map.
+ /// Creates a new challenge value with [scheme] and [parameters].
AuthenticationChallenge(this.scheme, Map<String, String> parameters)
: parameters = new UnmodifiableMapView(
new CaseInsensitiveMap.from(parameters));
diff --git a/lib/src/scan.dart b/lib/src/scan.dart
index 1036383..d675b8b 100644
--- a/lib/src/scan.dart
+++ b/lib/src/scan.dart
@@ -41,6 +41,12 @@
/// the string, or the end of the string.
List parseList(StringScanner scanner, parseElement()) {
var result = [];
+
+ // Consume initial empty values.
+ while (scanner.scan(",")) {
+ scanner.scan(whitespace);
+ }
+
result.add(parseElement());
scanner.scan(whitespace);
@@ -48,7 +54,7 @@
scanner.scan(whitespace);
// Empty elements are allowed, but excluded from the results.
- if (scanner.matches(",")) continue;
+ if (scanner.matches(",") || scanner.isDone) continue;
result.add(parseElement());
scanner.scan(whitespace);
diff --git a/test/authentication_challenge_test.dart b/test/authentication_challenge_test.dart
index ccb8d32..4be4444 100644
--- a/test/authentication_challenge_test.dart
+++ b/test/authentication_challenge_test.dart
@@ -94,6 +94,13 @@
expect(challenge.parameters, containsPair("realm", "fblthp"));
});
+ test("doesn't normalize the case of the parameter value", () {
+ var challenge = parseChallenge("scheme realm=FbLtHp");
+ expect(challenge.scheme, equals("scheme"));
+ expect(challenge.parameters, containsPair("realm", "FbLtHp"));
+ expect(challenge.parameters, isNot(containsPair("realm", "fblthp")));
+ });
+
test("allows extra whitespace", () {
var challenge = parseChallenge(
" scheme\t \trealm\t = \tfblthp\t, \tfoo\t\r\n =\tbar\t");
@@ -114,6 +121,26 @@
}));
});
+ test("allows a leading comma", () {
+ var challenge = parseChallenge(
+ "scheme , realm=fblthp, foo=bar,");
+ expect(challenge.scheme, equals("scheme"));
+ expect(challenge.parameters, equals({
+ "realm": "fblthp",
+ "foo": "bar"
+ }));
+ });
+
+ test("allows a trailing comma", () {
+ var challenge = parseChallenge(
+ "scheme realm=fblthp, foo=bar, ,");
+ expect(challenge.scheme, equals("scheme"));
+ expect(challenge.parameters, equals({
+ "realm": "fblthp",
+ "foo": "bar"
+ }));
+ });
+
test("disallows only a scheme", () {
expect(() => parseChallenge("scheme"),
throwsFormatException);