blob: f69ac8b194436c61db0439ad6b40d4cccf0cba79 [file] [log] [blame]
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
library utils;
import 'dart:async';
import 'dart:uri';
import 'dart:isolate';
import 'dart:crypto';
/// Adds additional query parameters to [url], overwriting the original
/// parameters if a name conflict occurs.
Uri addQueryParameters(Uri url, Map<String, String> parameters) {
var queryMap = queryToMap(url.query);
mapAddAll(queryMap, parameters);
return url.resolve("?${mapToQuery(queryMap)}");
}
/// Convert a URL query string (or `application/x-www-form-urlencoded` body)
/// into a [Map] from parameter names to values.
Map<String, String> queryToMap(String queryList) {
var map = {};
for (var pair in queryList.split("&")) {
var split = split1(pair, "=");
if (split.isEmpty) continue;
var key = urlDecode(split[0]);
var value = split.length > 1 ? urlDecode(split[1]) : "";
map[key] = value;
}
return map;
}
/// Convert a [Map] from parameter names to values to a URL query string.
String mapToQuery(Map<String, String> map) {
var pairs = <List<String>>[];
map.forEach((key, value) {
key = encodeUriComponent(key);
value = (value == null || value.isEmpty) ? null : encodeUriComponent(value);
pairs.add([key, value]);
});
return pairs.map((pair) {
if (pair[1] == null) return pair[0];
return "${pair[0]}=${pair[1]}";
}).join("&");
}
/// Add all key/value pairs from [source] to [destination], overwriting any
/// pre-existing values.
void mapAddAll(Map destination, Map source) =>
source.forEach((key, value) => destination[key] = value);
/// Decode a URL-encoded string. Unlike [decodeUriComponent], this includes
/// replacing `+` with ` `.
String urlDecode(String encoded) =>
decodeUriComponent(encoded.replaceAll("+", " "));
/// Like [String.split], but only splits on the first occurrence of the pattern.
/// This will always return a list of two elements or fewer.
List<String> split1(String toSplit, String pattern) {
if (toSplit.isEmpty) return <String>[];
var index = toSplit.indexOf(pattern);
if (index == -1) return [toSplit];
return [toSplit.substring(0, index),
toSplit.substring(index + pattern.length)];
}
/// A WWW-Authenticate header value, parsed as per [RFC 2617][].
///
/// [RFC 2617]: http://tools.ietf.org/html/rfc2617
class AuthenticateHeader {
final String scheme;
final Map<String, String> parameters;
AuthenticateHeader(this.scheme, this.parameters);
/// Parses a header string. Throws a [FormatException] if the header is
/// invalid.
factory AuthenticateHeader.parse(String header) {
var split = split1(header, ' ');
if (split.length == 0) {
throw new FormatException('Invalid WWW-Authenticate header: "$header"');
} else if (split.length == 1 || split[1].trim().isEmpty) {
return new AuthenticateHeader(split[0].toLowerCase(), {});
}
var scheme = split[0].toLowerCase();
var paramString = split[1];
// From http://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html.
var tokenChar = r'[^\0-\x1F()<>@,;:\\"/\[\]?={} \t\x7F]';
var quotedStringChar = r'(?:[^\0-\x1F\x7F"]|\\.)';
var regexp = new RegExp('^ *($tokenChar+)="($quotedStringChar*)" *(, *)?');
var parameters = {};
var match;
do {
match = regexp.firstMatch(paramString);
if (match == null) {
throw new FormatException('Invalid WWW-Authenticate header: "$header"');
}
paramString = paramString.substring(match.end);
parameters[match.group(1).toLowerCase()] = match.group(2);
} while (match.group(3) != null);
if (!paramString.trim().isEmpty) {
throw new FormatException('Invalid WWW-Authenticate header: "$header"');
}
return new AuthenticateHeader(scheme, parameters);
}
}
/// Returns a [Future] that asynchronously completes to `null`.
Future get async => new Future.delayed(const Duration(milliseconds: 0),
() => null);