Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(76)

Side by Side Diff: sdk/lib/core/uri.dart

Issue 23904004: Accept IPv6 addresses in Uri.http and Uri.https, and correctly nest IPv6 addresses in '[' and ']'. (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Fixing comments. Created 7 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « no previous file | tests/co19/co19-co19.status » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 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. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 part of dart.core; 5 part of dart.core;
6 6
7 /** 7 /**
8 * A parsed URI, as specified by RFC-3986, http://tools.ietf.org/html/rfc3986. 8 * A parsed URI, as specified by RFC-3986, http://tools.ietf.org/html/rfc3986.
9 */ 9 */
10 class Uri { 10 class Uri {
11 final String _host;
11 int _port; 12 int _port;
12 String _path; 13 String _path;
13 14
14 /** 15 /**
15 * Returns the scheme component. 16 * Returns the scheme component.
16 * 17 *
17 * Returns the empty string if there is no scheme component. 18 * Returns the empty string if there is no scheme component.
18 */ 19 */
19 final String scheme; 20 final String scheme;
20 21
(...skipping 18 matching lines...) Expand all
39 * Returns the empty string if there is no user info in the 40 * Returns the empty string if there is no user info in the
40 * authority component. 41 * authority component.
41 */ 42 */
42 final String userInfo; 43 final String userInfo;
43 44
44 /** 45 /**
45 * Returns the host part of the authority component. 46 * Returns the host part of the authority component.
46 * 47 *
47 * Returns the empty string if there is no authority component and 48 * Returns the empty string if there is no authority component and
48 * hence no host. 49 * hence no host.
50 *
51 * If the host is an IP version 6 address, the surrounding `[` and `]` is
52 * removed.
49 */ 53 */
50 final String host; 54 String get host {
55 if (_host != null && _host.startsWith('[')) {
56 return _host.substring(1, _host.length - 1);
57 }
58 return _host;
59 }
51 60
52 /** 61 /**
53 * Returns the port part of the authority component. 62 * Returns the port part of the authority component.
54 * 63 *
55 * Returns 0 if there is no port in the authority component. 64 * Returns 0 if there is no port in the authority component.
56 */ 65 */
57 int get port => _port; 66 int get port => _port;
58 67
59 /** 68 /**
60 * Returns the path component. 69 * Returns the path component.
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after
116 * default value of 0. The [path] and [query] components can be set 125 * default value of 0. The [path] and [query] components can be set
117 * using two different named arguments. 126 * using two different named arguments.
118 * 127 *
119 * The scheme component is set through [scheme]. The scheme is 128 * The scheme component is set through [scheme]. The scheme is
120 * normalized to all lowercase letters. 129 * normalized to all lowercase letters.
121 * 130 *
122 * The user info part of the authority component is set through 131 * The user info part of the authority component is set through
123 * [userInfo]. 132 * [userInfo].
124 * 133 *
125 * The host part of the authority component is set through 134 * The host part of the authority component is set through
126 * [host]. The host can either be a hostname, a IPv4 address or an 135 * [host]. The host can either be a hostname, an IPv4 address or an
127 * IPv6 address, contained in '[' and ']'. If the host contains a 136 * IPv6 address, contained in '[' and ']'. If the host contains a
128 * ':' character, the '[' and ']' are added if not already provided. 137 * ':' character, the '[' and ']' are added if not already provided.
129 * 138 *
130 * The port part of the authority component is set through 139 * The port part of the authority component is set through
131 * [port]. The port is normalized for scheme http and https where 140 * [port]. The port is normalized for scheme http and https where
132 * port 80 and port 443 respectively is set. 141 * port 80 and port 443 respectively is set.
133 * 142 *
134 * The path component is set through either [path] or 143 * The path component is set through either [path] or
135 * [pathSegments]. When [path] is used, the provided string is 144 * [pathSegments]. When [path] is used, the provided string is
136 * expected to be fully percent-encoded, and is used in its literal 145 * expected to be fully percent-encoded, and is used in its literal
137 * form. When [pathSegments] is used, each of the provided segments 146 * form. When [pathSegments] is used, each of the provided segments
138 * is percent-encoded and joined using the forward slash 147 * is percent-encoded and joined using the forward slash
139 * separator. The percent-encoding of the path segments encodes all 148 * separator. The percent-encoding of the path segments encodes all
140 * characters except for the unreserved characters and the following 149 * characters except for the unreserved characters and the following
141 * list of characters: `!$&'()*+,;=:@`. If the other components 150 * list of characters: `!$&'()*+,;=:@`. If the other components
142 * calls for an absolute path a leading slash `/` is prepended if 151 * calls for an absolute path a leading slash `/` is prepended if
143 * not already there. 152 * not already there.
144 * 153 *
145 * The query component is set through either [query] or 154 * The query component is set through either [query] or
146 * [queryParameters]. When [query] is used the provided string is 155 * [queryParameters]. When [query] is used the provided string is
147 * expected to be fully percent-encoded and is used in its literal 156 * expected to be fully percent-encoded and is used in its literal
148 * form. When [queryParameters] is used the query is built from the 157 * form. When [queryParameters] is used the query is built from the
149 * provided map. Each key and value in the map is percent-encoded 158 * provided map. Each key and value in the map is percent-encoded
150 * and joined using equal and ampersand characters. The 159 * and joined using equal and ampersand characters. The
151 * percent-encoding of the keys and values encodes all characters 160 * percent-encoding of the keys and values encodes all characters
152 * except for the unreserved characters. 161 * except for the unreserved characters.
153 * 162 *
154 * The fragment component is set through [fragment]. 163 * The fragment component is set through [fragment].
155 */ 164 */
156 Uri({scheme, 165 Uri({String scheme,
157 this.userInfo: "", 166 this.userInfo: "",
158 this.host: "", 167 String host: "",
159 port: 0, 168 port: 0,
160 String path, 169 String path,
161 Iterable<String> pathSegments, 170 Iterable<String> pathSegments,
162 String query, 171 String query,
163 Map<String, String> queryParameters, 172 Map<String, String> queryParameters,
164 fragment: ""}) : 173 fragment: ""}) :
165 scheme = _makeScheme(scheme), 174 scheme = _makeScheme(scheme),
175 _host = _makeHost(host),
166 query = _makeQuery(query, queryParameters), 176 query = _makeQuery(query, queryParameters),
167 fragment = _makeFragment(fragment) { 177 fragment = _makeFragment(fragment) {
168 // Perform scheme specific normalization. 178 // Perform scheme specific normalization.
169 if (scheme == "http" && port == 80) { 179 if (scheme == "http" && port == 80) {
170 _port = 0; 180 _port = 0;
171 } else if (scheme == "https" && port == 443) { 181 } else if (scheme == "https" && port == 443) {
172 _port = 0; 182 _port = 0;
173 } else { 183 } else {
174 _port = port; 184 _port = port;
175 } 185 }
(...skipping 54 matching lines...) Expand 10 before | Expand all | Expand 10 after
230 // Split off the user info. 240 // Split off the user info.
231 bool hasUserInfo = false; 241 bool hasUserInfo = false;
232 for (int i = 0; i < authority.length; i++) { 242 for (int i = 0; i < authority.length; i++) {
233 if (authority.codeUnitAt(i) == _AT_SIGN) { 243 if (authority.codeUnitAt(i) == _AT_SIGN) {
234 hasUserInfo = true; 244 hasUserInfo = true;
235 userInfo = authority.substring(0, i); 245 userInfo = authority.substring(0, i);
236 hostStart = i + 1; 246 hostStart = i + 1;
237 break; 247 break;
238 } 248 }
239 } 249 }
250 var hostEnd = hostStart;
251 if (hostStart < authority.length &&
252 authority.codeUnitAt(hostStart) == _LEFT_BRACKET) {
253 // IPv6 host.
254 for (; hostEnd < authority.length; hostEnd++) {
255 if (authority.codeUnitAt(hostEnd) == _RIGHT_BRACKET) break;
256 }
257 if (hostEnd == authority.length) {
258 throw new FormatException("Invalid IPv6 host entry.");
259 }
260 parseIPv6Address(authority.substring(hostStart + 1, hostEnd));
261 hostEnd++; // Skip the closing bracket.
262 if (hostEnd != authority.length &&
263 authority.codeUnitAt(hostEnd) != _COLON) {
264 throw new FormatException("Invalid end of authority");
265 }
266 }
240 // Split host and port. 267 // Split host and port.
241 bool hasPort = false; 268 bool hasPort = false;
242 for (int i = hostStart; i < authority.length; i++) { 269 for (; hostEnd < authority.length; hostEnd++) {
243 if (authority.codeUnitAt(i) == _COLON) { 270 if (authority.codeUnitAt(hostEnd) == _COLON) {
244 hasPort = true; 271 var portString = authority.substring(hostEnd + 1);
245 host = authority.substring(hostStart, i); 272 // We allow the empty port - falling back to initial value.
246 if (!host.isEmpty) { 273 if (portString.isNotEmpty) port = int.parse(portString);
247 var portString = authority.substring(i + 1);
248 if (portString.isNotEmpty) port = int.parse(portString);
249 }
250 break; 274 break;
251 } 275 }
252 } 276 }
253 if (!hasPort) { 277 host = authority.substring(hostStart, hostEnd);
254 host = hasUserInfo ? authority.substring(hostStart) : authority;
255 }
256 278
257 return new Uri(scheme: scheme, 279 return new Uri(scheme: scheme,
258 userInfo: userInfo, 280 userInfo: userInfo,
259 host: host, 281 host: host,
260 port: port, 282 port: port,
261 pathSegments: unencodedPath.split("/"), 283 pathSegments: unencodedPath.split("/"),
262 queryParameters: queryParameters); 284 queryParameters: queryParameters);
263 } 285 }
264 286
265 /** 287 /**
(...skipping 201 matching lines...) Expand 10 before | Expand all | Expand 10 after
467 * The returned map is unmodifiable and will throw [UnsupportedError] on any 489 * The returned map is unmodifiable and will throw [UnsupportedError] on any
468 * calls that would mutate it. 490 * calls that would mutate it.
469 */ 491 */
470 Map<String, String> get queryParameters { 492 Map<String, String> get queryParameters {
471 if (_queryParameters == null) { 493 if (_queryParameters == null) {
472 _queryParameters = new _UnmodifiableMap(splitQueryString(query)); 494 _queryParameters = new _UnmodifiableMap(splitQueryString(query));
473 } 495 }
474 return _queryParameters; 496 return _queryParameters;
475 } 497 }
476 498
499 static String _makeHost(String host) {
500 if (host == null || host.isEmpty) return host;
501 if (host.codeUnitAt(0) == _LEFT_BRACKET) {
502 if (host.codeUnitAt(host.length - 1) != _RIGHT_BRACKET) {
503 throw new FormatException('Missing end `]` to match `[` in host');
504 }
505 parseIPv6Address(host.substring(1, host.length - 1));
506 return host;
507 }
508 for (int i = 0; i < host.length; i++) {
509 if (host.codeUnitAt(i) == _COLON) {
510 parseIPv6Address(host);
511 return '[$host]';
512 }
513 }
514 return host;
515 }
516
477 static String _makeScheme(String scheme) { 517 static String _makeScheme(String scheme) {
478 bool isSchemeLowerCharacter(int ch) { 518 bool isSchemeLowerCharacter(int ch) {
479 return ch < 128 && 519 return ch < 128 &&
480 ((_schemeLowerTable[ch >> 4] & (1 << (ch & 0x0f))) != 0); 520 ((_schemeLowerTable[ch >> 4] & (1 << (ch & 0x0f))) != 0);
481 } 521 }
482 522
483 bool isSchemeCharacter(int ch) { 523 bool isSchemeCharacter(int ch) {
484 return ch < 128 && ((_schemeTable[ch >> 4] & (1 << (ch & 0x0f))) != 0); 524 return ch < 128 && ((_schemeTable[ch >> 4] & (1 << (ch & 0x0f))) != 0);
485 } 525 }
486 526
(...skipping 334 matching lines...) Expand 10 before | Expand all | Expand 10 after
821 861
822 /** 862 /**
823 * Returns the origin of the URI in the form scheme://host:port for the 863 * Returns the origin of the URI in the form scheme://host:port for the
824 * schemes http and https. 864 * schemes http and https.
825 * 865 *
826 * It is an error if the scheme is not "http" or "https". 866 * It is an error if the scheme is not "http" or "https".
827 * 867 *
828 * See: http://www.w3.org/TR/2011/WD-html5-20110405/origin-0.html#origin 868 * See: http://www.w3.org/TR/2011/WD-html5-20110405/origin-0.html#origin
829 */ 869 */
830 String get origin { 870 String get origin {
831 if (scheme == "" || host == null || host == "") { 871 if (scheme == "" || _host == null || _host == "") {
832 throw new StateError("Cannot use origin without a scheme: $this"); 872 throw new StateError("Cannot use origin without a scheme: $this");
833 } 873 }
834 if (scheme != "http" && scheme != "https") { 874 if (scheme != "http" && scheme != "https") {
835 throw new StateError( 875 throw new StateError(
836 "Origin is only applicable schemes http and https: $this"); 876 "Origin is only applicable schemes http and https: $this");
837 } 877 }
838 if (port == 0) return "$scheme://$host"; 878 if (port == 0) return "$scheme://$_host";
839 return "$scheme://$host:$port"; 879 return "$scheme://$_host:$port";
840 } 880 }
841 881
842 /** 882 /**
843 * Returns the file path from a file URI. 883 * Returns the file path from a file URI.
844 * 884 *
845 * The returned path has either Windows or non-Windows 885 * The returned path has either Windows or non-Windows
846 * semantics. 886 * semantics.
847 * 887 *
848 * For non-Windows semantics the slash ("/") is used to separate 888 * For non-Windows semantics the slash ("/") is used to separate
849 * path segments. 889 * path segments.
(...skipping 105 matching lines...) Expand 10 before | Expand all | Expand 10 after
955 result.write(host); 995 result.write(host);
956 result.write("\\"); 996 result.write("\\");
957 } 997 }
958 result.writeAll(segments, "\\"); 998 result.writeAll(segments, "\\");
959 if (hasDriveLetter && segments.length == 1) result.write("\\"); 999 if (hasDriveLetter && segments.length == 1) result.write("\\");
960 return result.toString(); 1000 return result.toString();
961 } 1001 }
962 1002
963 void _writeAuthority(StringSink ss) { 1003 void _writeAuthority(StringSink ss) {
964 _addIfNonEmpty(ss, userInfo, userInfo, "@"); 1004 _addIfNonEmpty(ss, userInfo, userInfo, "@");
965 ss.write(host == null ? "null" : 1005 ss.write(_host == null ? "null" : _host);
966 host.contains(':') ? '[$host]' : host);
967 if (port != 0) { 1006 if (port != 0) {
968 ss.write(":"); 1007 ss.write(":");
969 ss.write(port.toString()); 1008 ss.write(port.toString());
970 } 1009 }
971 } 1010 }
972 1011
973 String toString() { 1012 String toString() {
974 StringBuffer sb = new StringBuffer(); 1013 StringBuffer sb = new StringBuffer();
975 _addIfNonEmpty(sb, scheme, scheme, ':'); 1014 _addIfNonEmpty(sb, scheme, scheme, ':');
976 if (hasAuthority || (scheme == "file")) { 1015 if (hasAuthority || (scheme == "file")) {
(...skipping 164 matching lines...) Expand 10 before | Expand all | Expand 10 after
1141 } else if (index != 0) { 1180 } else if (index != 0) {
1142 var key = element.substring(0, index); 1181 var key = element.substring(0, index);
1143 var value = element.substring(index + 1); 1182 var value = element.substring(index + 1);
1144 map[Uri.decodeQueryComponent(key, decode: decode)] = 1183 map[Uri.decodeQueryComponent(key, decode: decode)] =
1145 decodeQueryComponent(value, decode: decode); 1184 decodeQueryComponent(value, decode: decode);
1146 } 1185 }
1147 return map; 1186 return map;
1148 }); 1187 });
1149 } 1188 }
1150 1189
1190 /**
1191 * Parse the [host] as an IP version 4 (IPv4) address, returning the address
1192 * as a list of 4 bytes in network byte order (big endian).
1193 *
1194 * Throws a [FormatException] if [host] is not a valid IPv4 address
1195 * representation.
1196 */
1197 static List<int> parseIPv4Address(String host) {
1198 void error(String msg) {
1199 throw new FormatException('Illegal IPv4 address, $msg');
1200 }
1201 var bytes = host.split('.');
1202 if (bytes.length != 4) {
1203 error('IPv4 address should contain exactly 4 parts');
1204 }
1205 // TODO(ajohnsen): Consider using Uint8List.
1206 return bytes
1207 .map((byteString) {
1208 int byte = int.parse(byteString);
1209 if (byte < 0 || byte > 255) {
1210 error('each part must be in the range of `0..255`');
1211 }
1212 return byte;
1213 })
1214 .toList();
1215 }
1216
1217 /**
1218 * Parse the [host] as an IP version 6 (IPv6) address, returning the address
1219 * as a list of 16 bytes in network byte order (big endian).
1220 *
1221 * Throws a [FormatException] if [host] is not a valid IPv6 address
1222 * representation.
1223 *
1224 * Some examples of IPv6 addresses:
1225 * * ::1
1226 * * FEDC:BA98:7654:3210:FEDC:BA98:7654:3210
1227 * * 3ffe:2a00:100:7031::1
1228 * * ::FFFF:129.144.52.38
1229 * * 2010:836B:4179::836B:4179
1230 */
1231 static List<int> parseIPv6Address(String host) {
1232 // An IPv6 address consists of exactly 8 parts of 1-4 hex digits, seperated
1233 // by `:`'s, with the following exceptions:
1234 //
1235 // - One (and only one) wildcard (`::`) may be present, representing a fill
1236 // of 0's. The IPv6 `::` is thus 16 bytes of `0`.
1237 // - The last two parts may be replaced by an IPv4 address.
1238 void error(String msg) {
1239 throw new FormatException('Illegal IPv6 address, $msg');
1240 }
1241 int parseHex(int start, int end) {
1242 if (end - start > 4) {
1243 error('an IPv6 part can only contain a maximum of 4 hex digits');
1244 }
1245 int value = int.parse(host.substring(start, end), radix: 16);
1246 if (value < 0 || value > (1 << 16) - 1) {
1247 error('each part must be in the range of `0x0..0xFFFF`');
1248 }
1249 return value;
1250 }
1251 if (host.length < 2) error('address is too short');
1252 List<int> parts = [];
1253 bool wildcardSeen = false;
1254 int partStart = 0;
1255 // Parse all parts, except a potential last one.
1256 for (int i = 0; i < host.length; i++) {
1257 if (host.codeUnitAt(i) == _COLON) {
1258 if (i == 0) {
1259 // If we see a `:` in the beginning, expect wildcard.
1260 i++;
1261 if (host.codeUnitAt(i) != _COLON) {
1262 error('invalid start colon.');
1263 }
1264 partStart = i;
1265 }
1266 if (i == partStart) {
1267 // Wildcard. We only allow one.
1268 if (wildcardSeen) {
1269 error('only one wildcard `::` is allowed');
1270 }
1271 wildcardSeen = true;
1272 parts.add(-1);
1273 } else {
1274 // Found a single colon. Parse [partStart..i] as a hex entry.
1275 parts.add(parseHex(partStart, i));
1276 }
1277 partStart = i + 1;
1278 }
1279 }
1280 if (parts.length == 0) error('too few parts');
1281 bool atEnd = partStart == host.length;
1282 bool isLastWildcard = parts.last == -1;
1283 if (atEnd && !isLastWildcard) {
1284 error('expected a part after last `:`');
1285 }
1286 if (!atEnd) {
1287 try {
1288 parts.add(parseHex(partStart, host.length));
1289 } catch (e) {
1290 // Failed to parse the last chunk as hex. Try IPv4.
1291 try {
1292 List<int> last = parseIPv4Address(host.substring(partStart));
1293 parts.add(last[0] << 8 | last[1]);
1294 parts.add(last[2] << 8 | last[3]);
1295 } catch (e) {
1296 error('invalid end of IPv6 address.');
1297 }
1298 }
1299 }
1300 if (wildcardSeen) {
1301 if (parts.length > 7) {
1302 error('an address with a wildcard must have less than 7 parts');
1303 }
1304 } else if (parts.length != 8) {
1305 error('an address without a wildcard must contain exactly 8 parts');
1306 }
1307 // TODO(ajohnsen): Consider using Uint8List.
1308 return parts
1309 .expand((value) {
1310 if (value == -1) {
1311 return new List.filled((9 - parts.length) * 2, 0);
1312 } else {
1313 return [(value >> 8) & 0xFF, value & 0xFF];
1314 }
1315 })
1316 .toList();
1317 }
1318
1151 // Frequently used character codes. 1319 // Frequently used character codes.
1152 static const int _DOUBLE_QUOTE = 0x22; 1320 static const int _DOUBLE_QUOTE = 0x22;
1153 static const int _PERCENT = 0x25; 1321 static const int _PERCENT = 0x25;
1154 static const int _ASTERISK = 0x2A; 1322 static const int _ASTERISK = 0x2A;
1155 static const int _PLUS = 0x2B; 1323 static const int _PLUS = 0x2B;
1156 static const int _SLASH = 0x2F; 1324 static const int _SLASH = 0x2F;
1157 static const int _ZERO = 0x30; 1325 static const int _ZERO = 0x30;
1158 static const int _NINE = 0x39; 1326 static const int _NINE = 0x39;
1159 static const int _COLON = 0x3A; 1327 static const int _COLON = 0x3A;
1160 static const int _LESS = 0x3C; 1328 static const int _LESS = 0x3C;
1161 static const int _GREATER = 0x3E; 1329 static const int _GREATER = 0x3E;
1162 static const int _QUESTION = 0x3F; 1330 static const int _QUESTION = 0x3F;
1163 static const int _AT_SIGN = 0x40; 1331 static const int _AT_SIGN = 0x40;
1164 static const int _UPPER_CASE_A = 0x41; 1332 static const int _UPPER_CASE_A = 0x41;
1165 static const int _UPPER_CASE_F = 0x46; 1333 static const int _UPPER_CASE_F = 0x46;
1166 static const int _UPPER_CASE_Z = 0x5A; 1334 static const int _UPPER_CASE_Z = 0x5A;
1335 static const int _LEFT_BRACKET = 0x5B;
1167 static const int _BACKSLASH = 0x5C; 1336 static const int _BACKSLASH = 0x5C;
1337 static const int _RIGHT_BRACKET = 0x5D;
1168 static const int _LOWER_CASE_A = 0x61; 1338 static const int _LOWER_CASE_A = 0x61;
1169 static const int _LOWER_CASE_F = 0x66; 1339 static const int _LOWER_CASE_F = 0x66;
1170 static const int _LOWER_CASE_Z = 0x7A; 1340 static const int _LOWER_CASE_Z = 0x7A;
1171 static const int _BAR = 0x7C; 1341 static const int _BAR = 0x7C;
1172 1342
1173 /** 1343 /**
1174 * This is the internal implementation of JavaScript's encodeURI function. 1344 * This is the internal implementation of JavaScript's encodeURI function.
1175 * It encodes all characters in the string [text] except for those 1345 * It encodes all characters in the string [text] except for those
1176 * that appear in [canonicalTable], and returns the escaped string. 1346 * that appear in [canonicalTable], and returns the escaped string.
1177 */ 1347 */
(...skipping 287 matching lines...) Expand 10 before | Expand all | Expand 10 after
1465 void clear() { 1635 void clear() {
1466 throw new UnsupportedError("Cannot modify an unmodifiable map"); 1636 throw new UnsupportedError("Cannot modify an unmodifiable map");
1467 } 1637 }
1468 void forEach(void f(K key, V value)) => _map.forEach(f); 1638 void forEach(void f(K key, V value)) => _map.forEach(f);
1469 Iterable<K> get keys => _map.keys; 1639 Iterable<K> get keys => _map.keys;
1470 Iterable<V> get values => _map.values; 1640 Iterable<V> get values => _map.values;
1471 int get length => _map.length; 1641 int get length => _map.length;
1472 bool get isEmpty => _map.isEmpty; 1642 bool get isEmpty => _map.isEmpty;
1473 bool get isNotEmpty => _map.isNotEmpty; 1643 bool get isNotEmpty => _map.isNotEmpty;
1474 } 1644 }
OLDNEW
« no previous file with comments | « no previous file | tests/co19/co19-co19.status » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698