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

Unified 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, 5 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 side-by-side diff with in-line comments
Download patch
Index: sdk/lib/core/uri.dart
diff --git a/sdk/lib/core/uri.dart b/sdk/lib/core/uri.dart
index 73ce45b906d466733aa000276c1a4b5fa424c034..fbc4ba7845e69b6c597d15d43036c2b3e8c546ab 100644
--- a/sdk/lib/core/uri.dart
+++ b/sdk/lib/core/uri.dart
@@ -263,6 +263,191 @@ class Uri {
}
/**
+ * 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.
+ *
+ * The file path is passed in [path].
+ *
+ * This path is interpreted using either Windows or non-Windows
+ * semantics. For Windows semantics the backslash or slash separator
+ * 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.
+ *
+ * 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 ("\").
+ * segments.
+ *
+ * With Windows semantics both the backslash `\` and the slash `/`
+ * are is used to separate path segments. An exception is when the
+ * 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.
+ * 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.
+ *
+ * If the path starts with a path separator an absolute URI is
+ * created. Otherwise a relative URI is created. One exception from
+ * this rule is that with Windows semantics and a path which starts
+ * 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
+ *
+ * The default for whether to use Windows or non-Windows semantics
+ * determined from the platform Dart is running on. When running in
+ * the standalone VM this is detected by the VM based on the
+ * 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.
+ * semantics is always used.
+ *
+ * To override the automatic detection of which semantics to use pass
+ * a value for [windowsPath]. Passing `true` will use Windows
+ * semantics and passing `false` will use non-Windows semantics.
+ *
+ * Examples using non-Windows semantics (resulting URI in comment):
+ *
+ * new Uri.file("xxx/yyy"); // xxx/yyy
+ * new Uri.file("xxx/yyy/"); // xxx/yyy/
+ * new Uri.file("/xxx/yyy"); // file:///xxx/yyy
+ * new Uri.file("/xxx/yyy/"); // file:///xxx/yyy/
+ * new Uri.file("C:"); // C:
+ *
+ * Examples using Windows semantics (resulting URI in comment):
+ *
+ * new Uri.file(r"xxx\yyy"); // xxx/yyy
+ * new Uri.file(r"xxx\yyy\"); // xxx/yyy/
+ * new Uri.file(r"\xxx\yyy"); // file:///xxx/yyy
+ * new Uri.file(r"\xxx\yyy/"); // file:///xxx/yyy/
+ * new Uri.file(r"C:\xxx\yyy"); // file:///C:/xxx/yyy
+ * new Uri.file(r"C:xxx\yyy"); // file:///C:/xxx/yyy
+ * new Uri.file(r"\\server\share\file"); // file://server/share/file
+ * new Uri.file(r"C:"); // Throws as path with drive letter is not absolute.
ahe 2013/08/06 14:14:30 Long line.
Søren Gjesse 2013/08/07 12:08:00 Done.
+ *
+ * If the path passed is not a legal file path [ArgumentError] is thrown.
+ */
+ factory Uri.file(String path, {bool windowsPath}) {
+ 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.
+ return windowsPath ? _makeWindowsFileUrl(path) : _makeFileUri(path);
+ }
+
+ external static bool get _isWindowsPlatform;
+
+ 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.
+
+ // Characters which are not allowed in file names on Windows.
+ static final _windowsPathReservedCharacters = [
+ _DOUBLE_QUOTE,
+ _ASTERISK,
+ _SLASH,
+ _COLON, // Colon is only allowed after drive letter.
+ _LESS,
+ _GREATER,
+ _QUESTION,
+ _BACKSLASH, // Backslash can only be separator.
+ _BAR,
+ ];
+
+ static _checkNonWindowsPathReservedCharacters(List<String> segments,
+ bool argumentError) {
ahe 2013/08/06 14:14:30 Indentation.
Søren Gjesse 2013/08/07 12:08:00 Done.
+ for (var segment in segments) {
+ if (segment.contains("/")) {
+ if (argumentError) {
+ throw new ArgumentError("Illegal path character $segment");
+ } else {
+ throw new UnsupportedError("Illegal path character $segment");
+ }
+ }
+ }
+ }
+
+ static _checkWindowsPathReservedCharacters(List<String> segments,
+ bool argumentError,
+ [int firstSegment = 0]) {
+ for (int i = firstSegment; i < segments.length; i++) {
+ var segment = segments[i];
+ for (int j = 0; j < segment.length; j++) {
+ 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.
+ if (argumentError) {
+ throw new ArgumentError("Illegal path character ${segment[j]}");
+ } else {
+ throw new UnsupportedError("Illegal path character ${segment[j]}");
+ }
+ }
+ }
+ }
+ }
+
+ static _checkWindowsDriveLetter(int charCode, bool argumentError) {
+ if ((_UPPER_CASE_A <= charCode && charCode <= _UPPER_CASE_Z) ||
+ (_LOWER_CASE_A <= charCode && charCode <= _LOWER_CASE_Z)) {
+ return;
+ }
+ if (argumentError) {
+ throw new ArgumentError("Illegal drive letter " +
+ new String.fromCharCode(charCode));
+ } else {
+ throw new UnsupportedError("Illegal drive letter " +
+ new String.fromCharCode(charCode));
+ }
+ }
+
+ static _makeFileUri(String path) {
+ String sep = "/";
+ if (path.length > 0 && path[0] == sep) {
+ // Absolute file:// URI.
+ return new Uri(scheme: "file", pathSegments: path.split(sep));
+ } else {
+ // Relative URI.
+ return new Uri(pathSegments: path.split(sep));
+ }
+ }
+
+ static _makeWindowsFileUrl(String path) {
+ if (path.startsWith("\\\\?\\")) {
+ if (path.startsWith("\\\\?\\UNC\\")) {
+ path = "\\${path.substring(7)}";
+ } else {
+ path = path.substring(4);
+ if (path.length < 3 ||
+ path.codeUnitAt(1) != _COLON ||
+ path.codeUnitAt(2) != _BACKSLASH) {
+ throw new ArgumentError(
+ "Windows paths with \\\\?\\ prefix must be absolute");
+ }
+ }
+ } else {
+ path = path.replaceAll("/", "\\");
+ }
+ String sep = "\\";
+ if (path.length > 1 && path[1] == ":") {
+ _checkWindowsDriveLetter(path.codeUnitAt(0), true);
+ if (path.length == 2 || path.codeUnitAt(2) != _BACKSLASH) {
+ throw new ArgumentError(
+ "Windows paths with drive letter must be absolute");
+ }
+ // Absolute file://C:/ URI.
+ var pathSegments = path.split(sep);
+ _checkWindowsPathReservedCharacters(pathSegments, true, 1);
+ return new Uri(scheme: "file", pathSegments: pathSegments);
+ }
+
+ if (path.length > 0 && path[0] == sep) {
+ if (path.length > 1 && path[1] == sep) {
+ // Absolute file:// URI with host.
+ int pathStart = path.indexOf("\\", 2);
+ String hostPart =
+ pathStart == -1 ? path.substring(2) : path.substring(2, pathStart);
+ String pathPart =
+ pathStart == -1 ? "" : path.substring(pathStart + 1);
+ var pathSegments = pathPart.split(sep);
+ _checkWindowsPathReservedCharacters(pathSegments, true);
+ return new Uri(
+ scheme: "file", host: hostPart, pathSegments: pathSegments);
+ } else {
+ // Absolute file:// URI.
+ var pathSegments = path.split(sep);
+ _checkWindowsPathReservedCharacters(pathSegments, true);
+ return new Uri(scheme: "file", pathSegments: pathSegments);
+ }
+ } else {
+ // Relative URI.
+ var pathSegments = path.split(sep);
+ _checkWindowsPathReservedCharacters(pathSegments, true);
+ return new Uri(pathSegments: pathSegments);
+ }
+ }
+
+ /**
* Returns the URI path split into its segments. Each of the
* segments in the returned list have been decoded. If the path is
* empty the empty list will be returned. A leading slash `/` does
@@ -651,6 +836,120 @@ class Uri {
return "$scheme://$host:$port";
}
+ /**
+ * Returns the file path from a file URI.
+ *
+ * The returned path has either Windows or non-Windows
+ * semantics.
+ *
+ * For Windows semantics the backslash separator is used to separate
+ * path segments.
+ *
+ * For non-Windows semantics the slash is used to separate path
+ * segments.
+ *
+ * If the URI is absolute the path starts with a path separator
+ * unless Windows semantics is used and the first path segment is a
+ * drive letter. When Windows semantics is used a host component in
+ * the uri in interpreted as a file server and a UNC path is
+ * returned.
+ *
+ * The default for whether to use Windows or non-Windows semantics
+ * determined from the platform Dart is running on. When running in
+ * the standalone VM this is detected by the VM based on the
+ * 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.
+ * semantics is always used.
+ *
+ * To override the automatic detection of which semantics to use pass
+ * a value for [windowsPath]. Passing `true` will use Windows
+ * semantics and passing `false` will use non-Windows semantics.
+ *
+ * If the URI ends with a slash (i.e. the last path component is
+ * empty) the returned file path will also end with a slash.
+ *
+ * With Windows semantics URIs starting with a drive letter cannot
+ * be relative to the current drive on the designated drive. That is
+ * for the URI `file:///c:abc` calling `toFilePath` will return
+ * `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
+ *
+ * Examples using non-Windows semantics (resulting of calling toFilePath in comment):
ahe 2013/08/06 14:14:30 Long line.
Søren Gjesse 2013/08/07 12:08:00 Done.
+ *
+ * Uri.parse("xxx/yyy"); // xxx/yyy
+ * Uri.parse("xxx/yyy/"); // xxx/yyy/
+ * Uri.parse("file:///xxx/yyy"); // /xxx/yyy
+ * Uri.parse("file:///xxx/yyy/"); // /xxx/yyy/
+ * Uri.parse("file:///C:"); // /C:
+ * Uri.parse("file:///C:a"); // /C:a
+ *
+ * Examples using Windows semantics (resulting URI in comment):
+ *
+ * Uri.parse("xxx/yyy"); // xxx\yyy
+ * Uri.parse("xxx/yyy/"); // xxx\yyy\
+ * Uri.parse("file:///xxx/yyy"); // \xxx\yyy
+ * Uri.parse("file:///xxx/yyy/"); // \xxx\yyy/
+ * Uri.parse("file:///C:/xxx/yyy"); // C:\xxx\yyy
+ * Uri.parse("file:C:xxx/yyy"); // C:\xxx\yyy
+ * Uri.parse("file://server/share/file"); // \\server\share\file
+ *
+ * If the URI is not a file URI calling this throws
+ * [UnsupportedError].
+ *
+ * If the URI cannot be converted to a file path calling this throws
+ * [UnsupportedError].
+ */
+ String toFilePath({bool windowsPath}) {
+ if (scheme != "" && scheme != "file") {
+ throw new UnsupportedError(
+ "Cannot extract a file path from a $scheme URI");
+ }
+ if (windowsPath == null) windowsPath = _isWindows;
+ 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.
+ }
+
+ String _toFilePath() {
+ if (host != "") {
+ throw new UnsupportedError(
+ "Cannot extract a non-Windows file path from a file URI "
+ "with an authority");
+ }
+ _checkNonWindowsPathReservedCharacters(pathSegments, false);
+ var result = new StringBuffer();
+ if (isAbsolute) result.write("/");
+ result.writeAll(pathSegments, "/");
+ return result.toString();
+ }
+
+ String _toWindowsFilePath() {
+ bool hasDriveLetter = false;
+ var segments = pathSegments;
+ if (segments.length > 0 &&
+ segments[0].length >= 2 &&
+ segments[0].codeUnitAt(1) == _COLON) {
+ _checkWindowsDriveLetter(segments[0].codeUnitAt(0), false);
+ if (segments[0].length > 2) {
+ var originalSegments = segments;
+ segments = new List<String>(originalSegments.length + 1);
+ segments[0] = originalSegments[0].substring(0, 2);
+ segments[1] = originalSegments[0].substring(2);
+ segments.setRange(2, segments.length, originalSegments, 1);
+ }
+ _checkWindowsPathReservedCharacters(segments, false, 1);
+ hasDriveLetter = true;
+ } else {
+ _checkWindowsPathReservedCharacters(segments, false);
+ }
+ var result = new StringBuffer();
+ if (isAbsolute && !hasDriveLetter) result.write("\\");
+ if (host != "") {
+ result.write("\\");
+ result.write(host);
+ result.write("\\");
+ }
+ result.writeAll(segments, "\\");
+ if (hasDriveLetter && segments.length == 1) result.write("\\");
+ return result.toString();
+ }
+
void _writeAuthority(StringSink ss) {
_addIfNonEmpty(ss, userInfo, userInfo, "@");
ss.write(host == null ? "null" :
@@ -840,17 +1139,26 @@ class Uri {
}
// Frequently used character codes.
+ static const int _DOUBLE_QUOTE = 0x22;
static const int _PERCENT = 0x25;
+ static const int _ASTERISK = 0x2A;
static const int _PLUS = 0x2B;
static const int _SLASH = 0x2F;
static const int _ZERO = 0x30;
static const int _NINE = 0x39;
static const int _COLON = 0x3A;
+ static const int _LESS = 0x3C;
+ static const int _GREATER = 0x3E;
+ static const int _QUESTION = 0x3F;
static const int _AT_SIGN = 0x40;
static const int _UPPER_CASE_A = 0x41;
static const int _UPPER_CASE_F = 0x46;
+ static const int _UPPER_CASE_Z = 0x5A;
+ static const int _BACKSLASH = 0x5C;
static const int _LOWER_CASE_A = 0x61;
static const int _LOWER_CASE_F = 0x66;
+ static const int _LOWER_CASE_Z = 0x7A;
+ static const int _BAR = 0x7C;
/**
* This is the internal implementation of JavaScript's encodeURI function.

Powered by Google App Engine
This is Rietveld 408576698