Skip to content

Commit 69ba6e5

Browse files
lrhncommit-bot@chromium.org
authored andcommitted
Make FileSystemEntity.absolutePath understand more Windows paths.
Change-Id: I3bd1e885a5bbc118e80e9bce88cb2c216318de93 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/155126 Commit-Queue: Lasse R.H. Nielsen <[email protected]> Reviewed-by: Zichang Guo <[email protected]> Reviewed-by: Jonas Termansen <[email protected]>
1 parent 202e32a commit 69ba6e5

File tree

6 files changed

+203
-34
lines changed

6 files changed

+203
-34
lines changed

runtime/bin/file_win.cc

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -796,13 +796,18 @@ bool File::SetLastModified(Namespace* namespc,
796796
return _wutime64(system_name.wide(), &times) == 0;
797797
}
798798

799+
// Keep this function synchronized with the behavior
800+
// of `FileSystemEntity.isAbsolute` in file_system_entity.dart.
799801
bool File::IsAbsolutePath(const char* pathname) {
800-
// Should we consider network paths?
801-
if (pathname == NULL) {
802-
return false;
803-
}
804-
return ((strlen(pathname) > 2) && (pathname[1] == ':') &&
805-
((pathname[2] == '\\') || (pathname[2] == '/')));
802+
if (pathname == NULL) return false;
803+
char first = pathname[0];
804+
if (pathname == 0) return false;
805+
char second = pathname[1];
806+
if (first == '\\' && second == '\\') return true;
807+
if (second != ':') return false;
808+
first |= 0x20;
809+
char third = pathname[2];
810+
return (first >= 'a') && (first <= 'z') && (third == '\\' || third == '/');
806811
}
807812

808813
const char* File::GetCanonicalPath(Namespace* namespc,

sdk/lib/io/directory_impl.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ class _Directory extends FileSystemEntity implements Directory {
7070
if (result is ArgumentError) throw result;
7171
if (result is OSError) {
7272
throw new FileSystemException(
73-
"Setting current working directory failed", path, result);
73+
"Setting current working directory failed", path.toString(), result);
7474
}
7575
}
7676

sdk/lib/io/file_system_entity.dart

Lines changed: 92 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,10 @@ FileStat: type $type
240240
* files and directories.
241241
*/
242242
abstract class FileSystemEntity {
243+
static const _backslashChar = 0x5c;
244+
static const _slashChar = 0x2f;
245+
static const _colonChar = 0x3a;
246+
243247
String get _path;
244248
Uint8List get _rawPath;
245249

@@ -531,16 +535,31 @@ abstract class FileSystemEntity {
531535
}
532536

533537
static final RegExp _absoluteWindowsPathPattern =
534-
new RegExp(r'^(\\\\|[a-zA-Z]:[/\\])');
538+
new RegExp(r'^(?:\\\\|[a-zA-Z]:[/\\])');
535539

536540
/**
537-
* Returns a [bool] indicating whether this object's path is absolute.
541+
* Whether this object's path is absolute.
542+
*
543+
* An absolute path is independent of the current working
544+
* directory ([Directory.current]).
545+
* A non-absolute path must be interpreted relative to
546+
* the current working directory.
547+
*
548+
* On Windows, a path is absolute if it starts with `\\`
549+
* (two backslashesor representing a UNC path) or with a drive letter
550+
* between `a` and `z` (upper or lower case) followed by `:\` or `:/`.
551+
* The makes, for example, `\file.ext` a non-absolute path
552+
* because it depends on the current drive letter.
553+
*
554+
* On non-Windows, a path is absolute if it starts with `/`.
538555
*
539-
* On Windows, a path is absolute if it starts with \\\\ or a drive letter
540-
* between a and z (upper or lower case) followed by :\\ or :/.
541-
* On non-Windows, a path is absolute if it starts with /.
556+
* If the path is not absolute, use [absolute] to get an entity
557+
* with an absolute path referencing the same object in the file system,
558+
* if possible.
542559
*/
543-
bool get isAbsolute {
560+
bool get isAbsolute => _isAbsolute(path);
561+
562+
static bool _isAbsolute(String path) {
544563
if (Platform.isWindows) {
545564
return path.startsWith(_absoluteWindowsPathPattern);
546565
} else {
@@ -553,37 +572,86 @@ abstract class FileSystemEntity {
553572
*
554573
* The type of the returned instance is the type of [this].
555574
*
556-
* The absolute path is computed by prefixing
557-
* a relative path with the current working directory, and returning
558-
* an absolute path unchanged.
575+
* A file system entity with an already absolute path
576+
* (as reported by [isAbsolute]) is returned directly.
577+
* For a non-absolute path, the returned entity is absolute ([isAbsolute])
578+
* *if possible*, but still refers to the same file system object.
559579
*/
560580
FileSystemEntity get absolute;
561581

562582
String get _absolutePath {
563583
if (isAbsolute) return path;
584+
if (Platform.isWindows) return _absoluteWindowsPath(path);
564585
String current = Directory.current.path;
565-
if (current.endsWith('/') ||
566-
(Platform.isWindows && current.endsWith('\\'))) {
586+
if (current.endsWith('/')) {
567587
return '$current$path';
568588
} else {
569589
return '$current${Platform.pathSeparator}$path';
570590
}
571591
}
572592

573-
Uint8List get _rawAbsolutePath {
574-
if (isAbsolute) return _rawPath;
575-
var current = Directory.current._rawPath.toList();
576-
assert(current.last == 0);
577-
current.removeLast(); // Remove null terminator.
578-
if ((current.last == '/'.codeUnitAt(0)) ||
579-
(Platform.isWindows && (current.last == '\\'.codeUnitAt(0)))) {
580-
current.addAll(_rawPath);
581-
return new Uint8List.fromList(current);
582-
} else {
583-
current.addAll(utf8.encode(Platform.pathSeparator));
584-
current.addAll(_rawPath);
585-
return new Uint8List.fromList(current);
593+
/// The ASCII code of the Windows drive letter if [entity], if any.
594+
///
595+
/// Returns the ASCII code of the upper-cased drive letter of
596+
/// the path of [entity], if it has a drive letter (starts with `[a-zA-z]:`),
597+
/// or `-1` if it has no drive letter.
598+
static int _windowsDriveLetter(String path) {
599+
if (!path.startsWith(':', 1)) return -1;
600+
var first = path.codeUnitAt(0) & ~0x20;
601+
if (first >= 0x41 && first <= 0x5b) return first;
602+
return -1;
603+
}
604+
605+
/// The relative [path] converted to an absolute path.
606+
static String _absoluteWindowsPath(String path) {
607+
assert(Platform.isWindows);
608+
assert(!_isAbsolute(path));
609+
// Could perhaps use something like
610+
// https://docs.microsoft.com/en-us/windows/win32/api/pathcch/nf-pathcch-pathalloccombine
611+
var current = Directory.current.path;
612+
if (path.startsWith(r'\')) {
613+
assert(!path.startsWith(r'\', 1));
614+
// Absolute path, no drive letter.
615+
var currentDrive = _windowsDriveLetter(current);
616+
if (currentDrive >= 0) {
617+
return '${current[0]}:$path';
618+
}
619+
// If `current` is a UNC path \\server\share[...],
620+
// we make the absolute path relative to the share.
621+
// Also works with `\\?\c:\` paths.
622+
if (current.startsWith(r'\\')) {
623+
var serverEnd = current.indexOf(r'\', 2);
624+
if (serverEnd >= 0) {
625+
// We may want to recognize UNC paths of the form:
626+
// \\?\UNC\Server\share\...
627+
// specially, and be relative to the *share* not to UNC\.
628+
var shareEnd = current.indexOf(r'\', serverEnd + 1);
629+
if (shareEnd < 0) shareEnd = current.length;
630+
return '${current.substring(0, shareEnd)}$path';
631+
}
632+
}
633+
// If `current` is not in the drive-letter:path format,
634+
// or not \\server\share[\path],
635+
// we ignore it and return a relative path.
636+
return path;
637+
}
638+
var entityDrive = _windowsDriveLetter(path);
639+
if (entityDrive >= 0) {
640+
if (entityDrive != _windowsDriveLetter(current)) {
641+
// Need to resolve relative to current directory of the drive.
642+
// Windows remembers the last CWD of each drive.
643+
// We currently don't have that information, so we assume the root of that drive.
644+
return '${path[0]}:\\$path';
645+
}
646+
647+
/// A `c:relative\path` path on the same drive as `current`.
648+
path = path.substring(2);
649+
assert(!path.startsWith(r'\\'));
650+
}
651+
if (current.endsWith(r'\') || current.endsWith('/')) {
652+
return '$current$path';
586653
}
654+
return '$current\\$path';
587655
}
588656

589657
static bool _identicalSync(String path1, String path2) {

sdk/lib/io/link.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ abstract class Link implements FileSystemEntity {
5353
* On the Windows platform, this call will create a true symbolic link
5454
* instead of a Junction. In order to create a symbolic link on Windows, Dart
5555
* must be run in Administrator mode or the system must have Developer Mode
56-
* enabled, otherwise a [FileSystemException] will be raised with
56+
* enabled, otherwise a [FileSystemException] will be raised with
5757
* `ERROR_PRIVILEGE_NOT_HELD` set as the errno when this call is made.
5858
*
5959
* On other platforms, the posix symlink() call is used to make a symbolic
@@ -74,7 +74,7 @@ abstract class Link implements FileSystemEntity {
7474
* On the Windows platform, this call will create a true symbolic link
7575
* instead of a Junction. In order to create a symbolic link on Windows, Dart
7676
* must be run in Administrator mode or the system must have Developer Mode
77-
* enabled, otherwise a [FileSystemException] will be raised with
77+
* enabled, otherwise a [FileSystemException] will be raised with
7878
* `ERROR_PRIVILEGE_NOT_HELD` set as the errno when this call is made.
7979
*
8080
* On other platforms, the posix symlink() call is used to make a symbolic
@@ -172,7 +172,7 @@ class _Link extends FileSystemEntity implements Link {
172172

173173
bool existsSync() => FileSystemEntity._isLinkRawSync(_rawPath);
174174

175-
Link get absolute => new Link.fromRawPath(_rawAbsolutePath);
175+
Link get absolute => isAbsolute ? this : _Link(_absolutePath);
176176

177177
Future<Link> create(String target, {bool recursive: false}) {
178178
var result =

tests/standalone/io/file_test.dart

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1602,6 +1602,53 @@ class FileTest {
16021602
}
16031603
}
16041604

1605+
static void testAbsolute() {
1606+
var currentDirectory = Directory.current;
1607+
Directory.current = tempDirectory;
1608+
var file = File("temp.txt");
1609+
Expect.isFalse(file.isAbsolute);
1610+
file.writeAsStringSync("content");
1611+
1612+
var absFile = file.absolute;
1613+
Expect.isTrue(absFile.isAbsolute);
1614+
Expect.isTrue(absFile.path.startsWith(tempDirectory.path));
1615+
1616+
Expect.equals("content", absFile.readAsStringSync());
1617+
1618+
if (Platform.isWindows &&
1619+
tempDirectory.path.startsWith(RegExp(r"^[a-zA-Z]:"))) {
1620+
var driveRelativeFile = File(absFile.path.substring(2));
1621+
Expect.isFalse(driveRelativeFile.isAbsolute);
1622+
Expect.equals("content", driveRelativeFile.readAsStringSync());
1623+
1624+
var absFile3 = driveRelativeFile.absolute;
1625+
Expect.isTrue(absFile3.isAbsolute);
1626+
Expect.equals(absFile.path, absFile3.path);
1627+
Expect.equals("content", absFile3.readAsStringSync());
1628+
1629+
// Convert CWD from X:\path to \\localhost\X$\path.
1630+
var uncPath = r"\\localhost\" +
1631+
tempDirectory.path[0] +
1632+
r"$" +
1633+
tempDirectory.path.substring(2);
1634+
Directory.current = uncPath;
1635+
Expect.equals("content", file.readAsStringSync());
1636+
1637+
var absFile4 = file.absolute;
1638+
Expect.isTrue(absFile4.isAbsolute);
1639+
Expect.equals("content", absFile4.readAsStringSync());
1640+
1641+
Expect.equals("content", driveRelativeFile.readAsStringSync());
1642+
1643+
var absFile5 = driveRelativeFile.absolute;
1644+
Expect.isTrue(absFile5.isAbsolute);
1645+
Expect.isTrue(absFile5.path.startsWith(uncPath));
1646+
Expect.equals("content", absFile5.readAsStringSync());
1647+
}
1648+
file.deleteSync();
1649+
Directory.current = currentDirectory;
1650+
}
1651+
16051652
static String getFilename(String path) {
16061653
return Platform.script.resolve(path).toFilePath();
16071654
}
@@ -1677,6 +1724,7 @@ class FileTest {
16771724
testSetLastAccessedSync();
16781725
testSetLastAccessedSyncDirectory();
16791726
testDoubleAsyncOperation();
1727+
testAbsolute();
16801728
asyncEnd();
16811729
});
16821730
}

tests/standalone_2/io/file_test.dart

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1601,6 +1601,53 @@ class FileTest {
16011601
}
16021602
}
16031603

1604+
static void testAbsolute() {
1605+
var currentDirectory = Directory.current;
1606+
Directory.current = tempDirectory;
1607+
var file = File("temp.txt");
1608+
Expect.isFalse(file.isAbsolute);
1609+
file.writeAsStringSync("content");
1610+
1611+
var absFile = file.absolute;
1612+
Expect.isTrue(absFile.isAbsolute);
1613+
Expect.isTrue(absFile.path.startsWith(tempDirectory.path));
1614+
1615+
Expect.equals("content", absFile.readAsStringSync());
1616+
1617+
if (Platform.isWindows &&
1618+
tempDirectory.path.startsWith(RegExp(r"^[a-zA-Z]:"))) {
1619+
var driveRelativeFile = File(absFile.path.substring(2));
1620+
Expect.isFalse(driveRelativeFile.isAbsolute);
1621+
Expect.equals("content", driveRelativeFile.readAsStringSync());
1622+
1623+
var absFile3 = driveRelativeFile.absolute;
1624+
Expect.isTrue(absFile3.isAbsolute);
1625+
Expect.equals(absFile.path, absFile3.path);
1626+
Expect.equals("content", absFile3.readAsStringSync());
1627+
1628+
// Convert CWD from X:\path to \\localhost\X$\path.
1629+
var uncPath = r"\\localhost\" +
1630+
tempDirectory.path[0] +
1631+
r"$" +
1632+
tempDirectory.path.substring(2);
1633+
Directory.current = uncPath;
1634+
Expect.equals("content", file.readAsStringSync());
1635+
1636+
var absFile4 = file.absolute;
1637+
Expect.isTrue(absFile4.isAbsolute);
1638+
Expect.equals("content", absFile4.readAsStringSync());
1639+
1640+
Expect.equals("content", driveRelativeFile.readAsStringSync());
1641+
1642+
var absFile5 = driveRelativeFile.absolute;
1643+
Expect.isTrue(absFile5.isAbsolute);
1644+
Expect.isTrue(absFile5.path.startsWith(uncPath));
1645+
Expect.equals("content", absFile5.readAsStringSync());
1646+
}
1647+
file.deleteSync();
1648+
Directory.current = currentDirectory;
1649+
}
1650+
16041651
static String getFilename(String path) {
16051652
return Platform.script.resolve(path).toFilePath();
16061653
}
@@ -1676,6 +1723,7 @@ class FileTest {
16761723
testSetLastAccessedSync();
16771724
testSetLastAccessedSyncDirectory();
16781725
testDoubleAsyncOperation();
1726+
testAbsolute();
16791727
asyncEnd();
16801728
});
16811729
}

0 commit comments

Comments
 (0)