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 21039005: Add support for file URIs and extracting the file path from file URIs (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Addressed first round of review comments Created 7 years, 4 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
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 {
(...skipping 245 matching lines...) Expand 10 before | Expand all | Expand 10 after
256 256
257 return new Uri(scheme: scheme, 257 return new Uri(scheme: scheme,
258 userInfo: userInfo, 258 userInfo: userInfo,
259 host: host, 259 host: host,
260 port: port, 260 port: port,
261 pathSegments: unencodedPath.split("/"), 261 pathSegments: unencodedPath.split("/"),
262 queryParameters: queryParameters); 262 queryParameters: queryParameters);
263 } 263 }
264 264
265 /** 265 /**
266 * Creates a new `file` URI from an absolute or relative file path.
ahe 2013/08/06 14:14:30 I think backquotes are for code snippets, I'm not
Søren Gjesse 2013/08/07 12:08:00 Done.
267 *
268 * The file path is passed in [path].
269 *
270 * This path is interpreted using either Windows or non-Windows
271 * semantics. For Windows semantics the backslash or slash separator
272 * is used to separate path segments.
ahe 2013/08/06 14:14:30 Remove the rest of this paragraph from "For Window
Søren Gjesse 2013/08/07 12:08:00 Done.
273 *
274 * With non-Windows semantics the slash `/` is used to separate path
ahe 2013/08/06 14:14:30 I'm not sure about this use of backquotes either.
Søren Gjesse 2013/08/07 12:08:00 Ended up using ("/") and ("\").
275 * segments.
276 *
277 * With Windows semantics both the backslash `\` and the slash `/`
278 * are is used to separate path segments. An exception is when the
279 * path is prefixed with `\\?\` in which case only the backslash `\`
ahe 2013/08/06 14:14:30 You could replace "is prefixed with" by "starts wi
Søren Gjesse 2013/08/07 12:08:00 Thanks for rephrasing.
280 * is treated as a path separator.
ahe 2013/08/06 14:14:30 Ditto for the backquotes in above paragraph.
Søren Gjesse 2013/08/07 12:08:00 Done.
281 *
282 * If the path starts with a path separator an absolute URI is
283 * created. Otherwise a relative URI is created. One exception from
284 * this rule is that with Windows semantics and a path which starts
285 * with a drive letter then an absolute URI is created.
ahe 2013/08/06 14:14:30 This specification does not match the example belo
Søren Gjesse 2013/08/07 12:08:00 Extended the comment to say that the drive letter
286 *
287 * The default for whether to use Windows or non-Windows semantics
288 * determined from the platform Dart is running on. When running in
289 * the standalone VM this is detected by the VM based on the
290 * operating system. When running in the browser non-Windows
ahe 2013/08/06 14:14:30 "the browser" -> "a browser".
Søren Gjesse 2013/08/07 12:08:00 Done.
291 * semantics is always used.
292 *
293 * To override the automatic detection of which semantics to use pass
294 * a value for [windowsPath]. Passing `true` will use Windows
295 * semantics and passing `false` will use non-Windows semantics.
296 *
297 * Examples using non-Windows semantics (resulting URI in comment):
298 *
299 * new Uri.file("xxx/yyy"); // xxx/yyy
300 * new Uri.file("xxx/yyy/"); // xxx/yyy/
301 * new Uri.file("/xxx/yyy"); // file:///xxx/yyy
302 * new Uri.file("/xxx/yyy/"); // file:///xxx/yyy/
303 * new Uri.file("C:"); // C:
304 *
305 * Examples using Windows semantics (resulting URI in comment):
306 *
307 * new Uri.file(r"xxx\yyy"); // xxx/yyy
308 * new Uri.file(r"xxx\yyy\"); // xxx/yyy/
309 * new Uri.file(r"\xxx\yyy"); // file:///xxx/yyy
310 * new Uri.file(r"\xxx\yyy/"); // file:///xxx/yyy/
311 * new Uri.file(r"C:\xxx\yyy"); // file:///C:/xxx/yyy
312 * new Uri.file(r"C:xxx\yyy"); // file:///C:/xxx/yyy
313 * new Uri.file(r"\\server\share\file"); // file://server/share/file
314 * new Uri.file(r"C:"); // Throws as path with drive letter is not absolu te.
ahe 2013/08/06 14:14:30 Long line.
Søren Gjesse 2013/08/07 12:08:00 Done.
315 *
316 * If the path passed is not a legal file path [ArgumentError] is thrown.
317 */
318 factory Uri.file(String path, {bool windowsPath}) {
319 windowsPath = windowsPath == null ? Uri._isWindows : windowsPath;
ahe 2013/08/06 14:14:30 Remove Uri. prefix.
Søren Gjesse 2013/08/07 12:08:00 Done.
320 return windowsPath ? _makeWindowsFileUrl(path) : _makeFileUri(path);
321 }
322
323 external static bool get _isWindowsPlatform;
324
325 static final bool _isWindows = _isWindowsPlatform;
ahe 2013/08/06 14:14:30 For dart2js, this code is suboptimal. I think it w
Søren Gjesse 2013/08/07 12:08:00 Done.
326
327 // Characters which are not allowed in file names on Windows.
328 static final _windowsPathReservedCharacters = [
329 _DOUBLE_QUOTE,
330 _ASTERISK,
331 _SLASH,
332 _COLON, // Colon is only allowed after drive letter.
333 _LESS,
334 _GREATER,
335 _QUESTION,
336 _BACKSLASH, // Backslash can only be separator.
337 _BAR,
338 ];
339
340 static _checkNonWindowsPathReservedCharacters(List<String> segments,
341 bool argumentError) {
ahe 2013/08/06 14:14:30 Indentation.
Søren Gjesse 2013/08/07 12:08:00 Done.
342 for (var segment in segments) {
343 if (segment.contains("/")) {
344 if (argumentError) {
345 throw new ArgumentError("Illegal path character $segment");
346 } else {
347 throw new UnsupportedError("Illegal path character $segment");
348 }
349 }
350 }
351 }
352
353 static _checkWindowsPathReservedCharacters(List<String> segments,
354 bool argumentError,
355 [int firstSegment = 0]) {
356 for (int i = firstSegment; i < segments.length; i++) {
357 var segment = segments[i];
358 for (int j = 0; j < segment.length; j++) {
359 if (_windowsPathReservedCharacters.contains(segment.codeUnitAt(j))) {
ahe 2013/08/06 14:14:30 I suspect that regular expressions would be faster
Søren Gjesse 2013/08/07 12:08:00 Done.
360 if (argumentError) {
361 throw new ArgumentError("Illegal path character ${segment[j]}");
362 } else {
363 throw new UnsupportedError("Illegal path character ${segment[j]}");
364 }
365 }
366 }
367 }
368 }
369
370 static _checkWindowsDriveLetter(int charCode, bool argumentError) {
371 if ((_UPPER_CASE_A <= charCode && charCode <= _UPPER_CASE_Z) ||
372 (_LOWER_CASE_A <= charCode && charCode <= _LOWER_CASE_Z)) {
373 return;
374 }
375 if (argumentError) {
376 throw new ArgumentError("Illegal drive letter " +
377 new String.fromCharCode(charCode));
378 } else {
379 throw new UnsupportedError("Illegal drive letter " +
380 new String.fromCharCode(charCode));
381 }
382 }
383
384 static _makeFileUri(String path) {
385 String sep = "/";
386 if (path.length > 0 && path[0] == sep) {
387 // Absolute file:// URI.
388 return new Uri(scheme: "file", pathSegments: path.split(sep));
389 } else {
390 // Relative URI.
391 return new Uri(pathSegments: path.split(sep));
392 }
393 }
394
395 static _makeWindowsFileUrl(String path) {
396 if (path.startsWith("\\\\?\\")) {
397 if (path.startsWith("\\\\?\\UNC\\")) {
398 path = "\\${path.substring(7)}";
399 } else {
400 path = path.substring(4);
401 if (path.length < 3 ||
402 path.codeUnitAt(1) != _COLON ||
403 path.codeUnitAt(2) != _BACKSLASH) {
404 throw new ArgumentError(
405 "Windows paths with \\\\?\\ prefix must be absolute");
406 }
407 }
408 } else {
409 path = path.replaceAll("/", "\\");
410 }
411 String sep = "\\";
412 if (path.length > 1 && path[1] == ":") {
413 _checkWindowsDriveLetter(path.codeUnitAt(0), true);
414 if (path.length == 2 || path.codeUnitAt(2) != _BACKSLASH) {
415 throw new ArgumentError(
416 "Windows paths with drive letter must be absolute");
417 }
418 // Absolute file://C:/ URI.
419 var pathSegments = path.split(sep);
420 _checkWindowsPathReservedCharacters(pathSegments, true, 1);
421 return new Uri(scheme: "file", pathSegments: pathSegments);
422 }
423
424 if (path.length > 0 && path[0] == sep) {
425 if (path.length > 1 && path[1] == sep) {
426 // Absolute file:// URI with host.
427 int pathStart = path.indexOf("\\", 2);
428 String hostPart =
429 pathStart == -1 ? path.substring(2) : path.substring(2, pathStart);
430 String pathPart =
431 pathStart == -1 ? "" : path.substring(pathStart + 1);
432 var pathSegments = pathPart.split(sep);
433 _checkWindowsPathReservedCharacters(pathSegments, true);
434 return new Uri(
435 scheme: "file", host: hostPart, pathSegments: pathSegments);
436 } else {
437 // Absolute file:// URI.
438 var pathSegments = path.split(sep);
439 _checkWindowsPathReservedCharacters(pathSegments, true);
440 return new Uri(scheme: "file", pathSegments: pathSegments);
441 }
442 } else {
443 // Relative URI.
444 var pathSegments = path.split(sep);
445 _checkWindowsPathReservedCharacters(pathSegments, true);
446 return new Uri(pathSegments: pathSegments);
447 }
448 }
449
450 /**
266 * Returns the URI path split into its segments. Each of the 451 * Returns the URI path split into its segments. Each of the
267 * segments in the returned list have been decoded. If the path is 452 * segments in the returned list have been decoded. If the path is
268 * empty the empty list will be returned. A leading slash `/` does 453 * empty the empty list will be returned. A leading slash `/` does
269 * not affect the segments returned. 454 * not affect the segments returned.
270 * 455 *
271 * The returned list is unmodifiable and will throw [UnsupportedError] on any 456 * The returned list is unmodifiable and will throw [UnsupportedError] on any
272 * calls that would mutate it. 457 * calls that would mutate it.
273 */ 458 */
274 List<String> get pathSegments { 459 List<String> get pathSegments {
275 if (_pathSegments == null) { 460 if (_pathSegments == null) {
(...skipping 368 matching lines...) Expand 10 before | Expand all | Expand 10 after
644 throw new StateError("Cannot use origin without a scheme: $this"); 829 throw new StateError("Cannot use origin without a scheme: $this");
645 } 830 }
646 if (scheme != "http" && scheme != "https") { 831 if (scheme != "http" && scheme != "https") {
647 throw new StateError( 832 throw new StateError(
648 "Origin is only applicable schemes http and https: $this"); 833 "Origin is only applicable schemes http and https: $this");
649 } 834 }
650 if (port == 0) return "$scheme://$host"; 835 if (port == 0) return "$scheme://$host";
651 return "$scheme://$host:$port"; 836 return "$scheme://$host:$port";
652 } 837 }
653 838
839 /**
840 * Returns the file path from a file URI.
841 *
842 * The returned path has either Windows or non-Windows
843 * semantics.
844 *
845 * For Windows semantics the backslash separator is used to separate
846 * path segments.
847 *
848 * For non-Windows semantics the slash is used to separate path
849 * segments.
850 *
851 * If the URI is absolute the path starts with a path separator
852 * unless Windows semantics is used and the first path segment is a
853 * drive letter. When Windows semantics is used a host component in
854 * the uri in interpreted as a file server and a UNC path is
855 * returned.
856 *
857 * The default for whether to use Windows or non-Windows semantics
858 * determined from the platform Dart is running on. When running in
859 * the standalone VM this is detected by the VM based on the
860 * operating system. When running in the browser non-Windows
ahe 2013/08/06 14:14:30 the browser -> a browser.
Søren Gjesse 2013/08/07 12:08:00 Done.
861 * semantics is always used.
862 *
863 * To override the automatic detection of which semantics to use pass
864 * a value for [windowsPath]. Passing `true` will use Windows
865 * semantics and passing `false` will use non-Windows semantics.
866 *
867 * If the URI ends with a slash (i.e. the last path component is
868 * empty) the returned file path will also end with a slash.
869 *
870 * With Windows semantics URIs starting with a drive letter cannot
871 * be relative to the current drive on the designated drive. That is
872 * for the URI `file:///c:abc` calling `toFilePath` will return
873 * `c:\abc` and *not* `c:abc`.
ahe 2013/08/06 14:14:30 I thought this would throw.
Søren Gjesse 2013/08/07 12:08:00 It now throws. As discussed before I was not sure
874 *
875 * Examples using non-Windows semantics (resulting of calling toFilePath in co mment):
ahe 2013/08/06 14:14:30 Long line.
Søren Gjesse 2013/08/07 12:08:00 Done.
876 *
877 * Uri.parse("xxx/yyy"); // xxx/yyy
878 * Uri.parse("xxx/yyy/"); // xxx/yyy/
879 * Uri.parse("file:///xxx/yyy"); // /xxx/yyy
880 * Uri.parse("file:///xxx/yyy/"); // /xxx/yyy/
881 * Uri.parse("file:///C:"); // /C:
882 * Uri.parse("file:///C:a"); // /C:a
883 *
884 * Examples using Windows semantics (resulting URI in comment):
885 *
886 * Uri.parse("xxx/yyy"); // xxx\yyy
887 * Uri.parse("xxx/yyy/"); // xxx\yyy\
888 * Uri.parse("file:///xxx/yyy"); // \xxx\yyy
889 * Uri.parse("file:///xxx/yyy/"); // \xxx\yyy/
890 * Uri.parse("file:///C:/xxx/yyy"); // C:\xxx\yyy
891 * Uri.parse("file:C:xxx/yyy"); // C:\xxx\yyy
892 * Uri.parse("file://server/share/file"); // \\server\share\file
893 *
894 * If the URI is not a file URI calling this throws
895 * [UnsupportedError].
896 *
897 * If the URI cannot be converted to a file path calling this throws
898 * [UnsupportedError].
899 */
900 String toFilePath({bool windowsPath}) {
901 if (scheme != "" && scheme != "file") {
902 throw new UnsupportedError(
903 "Cannot extract a file path from a $scheme URI");
904 }
905 if (windowsPath == null) windowsPath = _isWindows;
906 return windowsPath ? _toWindowsFilePath() : _toFilePath();
ahe 2013/08/06 14:14:30 You should probably check that all other fields bu
Søren Gjesse 2013/08/07 12:08:00 Done and added tests.
907 }
908
909 String _toFilePath() {
910 if (host != "") {
911 throw new UnsupportedError(
912 "Cannot extract a non-Windows file path from a file URI "
913 "with an authority");
914 }
915 _checkNonWindowsPathReservedCharacters(pathSegments, false);
916 var result = new StringBuffer();
917 if (isAbsolute) result.write("/");
918 result.writeAll(pathSegments, "/");
919 return result.toString();
920 }
921
922 String _toWindowsFilePath() {
923 bool hasDriveLetter = false;
924 var segments = pathSegments;
925 if (segments.length > 0 &&
926 segments[0].length >= 2 &&
927 segments[0].codeUnitAt(1) == _COLON) {
928 _checkWindowsDriveLetter(segments[0].codeUnitAt(0), false);
929 if (segments[0].length > 2) {
930 var originalSegments = segments;
931 segments = new List<String>(originalSegments.length + 1);
932 segments[0] = originalSegments[0].substring(0, 2);
933 segments[1] = originalSegments[0].substring(2);
934 segments.setRange(2, segments.length, originalSegments, 1);
935 }
936 _checkWindowsPathReservedCharacters(segments, false, 1);
937 hasDriveLetter = true;
938 } else {
939 _checkWindowsPathReservedCharacters(segments, false);
940 }
941 var result = new StringBuffer();
942 if (isAbsolute && !hasDriveLetter) result.write("\\");
943 if (host != "") {
944 result.write("\\");
945 result.write(host);
946 result.write("\\");
947 }
948 result.writeAll(segments, "\\");
949 if (hasDriveLetter && segments.length == 1) result.write("\\");
950 return result.toString();
951 }
952
654 void _writeAuthority(StringSink ss) { 953 void _writeAuthority(StringSink ss) {
655 _addIfNonEmpty(ss, userInfo, userInfo, "@"); 954 _addIfNonEmpty(ss, userInfo, userInfo, "@");
656 ss.write(host == null ? "null" : 955 ss.write(host == null ? "null" :
657 host.contains(':') ? '[$host]' : host); 956 host.contains(':') ? '[$host]' : host);
658 if (port != 0) { 957 if (port != 0) {
659 ss.write(":"); 958 ss.write(":");
660 ss.write(port.toString()); 959 ss.write(port.toString());
661 } 960 }
662 } 961 }
663 962
(...skipping 169 matching lines...) Expand 10 before | Expand all | Expand 10 after
833 var key = element.substring(0, index); 1132 var key = element.substring(0, index);
834 var value = element.substring(index + 1); 1133 var value = element.substring(index + 1);
835 map[Uri.decodeQueryComponent(key, decode: decode)] = 1134 map[Uri.decodeQueryComponent(key, decode: decode)] =
836 decodeQueryComponent(value, decode: decode); 1135 decodeQueryComponent(value, decode: decode);
837 } 1136 }
838 return map; 1137 return map;
839 }); 1138 });
840 } 1139 }
841 1140
842 // Frequently used character codes. 1141 // Frequently used character codes.
1142 static const int _DOUBLE_QUOTE = 0x22;
843 static const int _PERCENT = 0x25; 1143 static const int _PERCENT = 0x25;
1144 static const int _ASTERISK = 0x2A;
844 static const int _PLUS = 0x2B; 1145 static const int _PLUS = 0x2B;
845 static const int _SLASH = 0x2F; 1146 static const int _SLASH = 0x2F;
846 static const int _ZERO = 0x30; 1147 static const int _ZERO = 0x30;
847 static const int _NINE = 0x39; 1148 static const int _NINE = 0x39;
848 static const int _COLON = 0x3A; 1149 static const int _COLON = 0x3A;
1150 static const int _LESS = 0x3C;
1151 static const int _GREATER = 0x3E;
1152 static const int _QUESTION = 0x3F;
849 static const int _AT_SIGN = 0x40; 1153 static const int _AT_SIGN = 0x40;
850 static const int _UPPER_CASE_A = 0x41; 1154 static const int _UPPER_CASE_A = 0x41;
851 static const int _UPPER_CASE_F = 0x46; 1155 static const int _UPPER_CASE_F = 0x46;
1156 static const int _UPPER_CASE_Z = 0x5A;
1157 static const int _BACKSLASH = 0x5C;
852 static const int _LOWER_CASE_A = 0x61; 1158 static const int _LOWER_CASE_A = 0x61;
853 static const int _LOWER_CASE_F = 0x66; 1159 static const int _LOWER_CASE_F = 0x66;
1160 static const int _LOWER_CASE_Z = 0x7A;
1161 static const int _BAR = 0x7C;
854 1162
855 /** 1163 /**
856 * This is the internal implementation of JavaScript's encodeURI function. 1164 * This is the internal implementation of JavaScript's encodeURI function.
857 * It encodes all characters in the string [text] except for those 1165 * It encodes all characters in the string [text] except for those
858 * that appear in [canonicalTable], and returns the escaped string. 1166 * that appear in [canonicalTable], and returns the escaped string.
859 */ 1167 */
860 static String _uriEncode(List<int> canonicalTable, 1168 static String _uriEncode(List<int> canonicalTable,
861 String text, 1169 String text,
862 {bool spaceToPlus: false}) { 1170 {bool spaceToPlus: false}) {
863 byteToHex(int v) { 1171 byteToHex(int v) {
(...skipping 281 matching lines...) Expand 10 before | Expand all | Expand 10 after
1145 void clear() { 1453 void clear() {
1146 throw new UnsupportedError("Cannot modify an unmodifiable map"); 1454 throw new UnsupportedError("Cannot modify an unmodifiable map");
1147 } 1455 }
1148 void forEach(void f(K key, V value)) => _map.forEach(f); 1456 void forEach(void f(K key, V value)) => _map.forEach(f);
1149 Iterable<K> get keys => _map.keys; 1457 Iterable<K> get keys => _map.keys;
1150 Iterable<V> get values => _map.values; 1458 Iterable<V> get values => _map.values;
1151 int get length => _map.length; 1459 int get length => _map.length;
1152 bool get isEmpty => _map.isEmpty; 1460 bool get isEmpty => _map.isEmpty;
1153 bool get isNotEmpty => _map.isNotEmpty; 1461 bool get isNotEmpty => _map.isNotEmpty;
1154 } 1462 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698