Skip to content

Commit 12fce4b

Browse files
author
Brian Burkhalter
committedNov 14, 2023
8287843: File::getCanonicalFile doesn't work for \\?\C:\ style paths DOS device paths
Reviewed-by: alanb
1 parent 346dbd6 commit 12fce4b

File tree

5 files changed

+295
-86
lines changed

5 files changed

+295
-86
lines changed
 

‎src/java.base/windows/classes/java/io/WinNTFileSystem.java

+33
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
*/
4242
final class WinNTFileSystem extends FileSystem {
4343

44+
private static final String LONG_PATH_PREFIX = "\\\\?\\";
45+
4446
private final char slash;
4547
private final char altSlash;
4648
private final char semicolon;
@@ -60,6 +62,25 @@ final class WinNTFileSystem extends FileSystem {
6062
}
6163
}
6264

65+
// Strip a long path or UNC prefix and return the result.
66+
// If there is no such prefix, return the parameter passed in.
67+
private static String stripLongOrUNCPrefix(String path) {
68+
// if a prefix is present, remove it
69+
if (path.startsWith(LONG_PATH_PREFIX)) {
70+
if (path.startsWith("UNC\\", 4)) {
71+
path = "\\\\" + path.substring(8);
72+
} else {
73+
path = path.substring(4);
74+
// if only "UNC" remains, a trailing "\\" was likely removed
75+
if (path.equals("UNC")) {
76+
path = "\\\\";
77+
}
78+
}
79+
}
80+
81+
return path;
82+
}
83+
6384
WinNTFileSystem() {
6485
Properties props = GetPropertyAction.privilegedGetProperties();
6586
slash = props.getProperty("file.separator").charAt(0);
@@ -98,6 +119,7 @@ public char getPathSeparator() {
98119
This way we iterate through the whole pathname string only once. */
99120
@Override
100121
public String normalize(String path) {
122+
path = stripLongOrUNCPrefix(path);
101123
int n = path.length();
102124
char slash = this.slash;
103125
char altSlash = this.altSlash;
@@ -223,6 +245,8 @@ && isSlash(path.charAt(1))) {
223245

224246
@Override
225247
public int prefixLength(String path) {
248+
assert !path.startsWith(LONG_PATH_PREFIX);
249+
226250
char slash = this.slash;
227251
int n = path.length();
228252
if (n == 0) return 0;
@@ -242,6 +266,8 @@ public int prefixLength(String path) {
242266

243267
@Override
244268
public String resolve(String parent, String child) {
269+
assert !child.startsWith(LONG_PATH_PREFIX);
270+
245271
int pn = parent.length();
246272
if (pn == 0) return child;
247273
int cn = child.length();
@@ -320,6 +346,9 @@ public String fromURIPath(String path) {
320346

321347
@Override
322348
public boolean isAbsolute(File f) {
349+
String path = f.getPath();
350+
assert !path.startsWith(LONG_PATH_PREFIX);
351+
323352
int pl = f.getPrefixLength();
324353
return (((pl == 2) && (f.getPath().charAt(0) == slash))
325354
|| (pl == 3));
@@ -358,6 +387,8 @@ public boolean isInvalid(File f) {
358387
@Override
359388
public String resolve(File f) {
360389
String path = f.getPath();
390+
assert !path.startsWith(LONG_PATH_PREFIX);
391+
361392
int pl = f.getPrefixLength();
362393
if ((pl == 2) && (path.charAt(0) == slash))
363394
return path; /* UNC */
@@ -440,6 +471,8 @@ private String getDriveDirectory(char drive) {
440471

441472
@Override
442473
public String canonicalize(String path) throws IOException {
474+
assert !path.startsWith(LONG_PATH_PREFIX);
475+
443476
// If path is a drive letter only then skip canonicalization
444477
int len = path.length();
445478
if ((len == 2) &&
+63-48
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 1998, 2001, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 1998, 2023, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -22,74 +22,89 @@
2222
*/
2323

2424
/* @test
25-
@bug 4131169 4109131
26-
@summary Basic test for getAbsolutePath method
25+
* @bug 4131169 4109131 8287843
26+
* @summary Basic test for getAbsolutePath method
27+
* @run junit GetAbsolutePath
2728
*/
2829

29-
import java.io.*;
30+
import java.io.File;
31+
import java.io.IOException;
32+
import java.util.stream.Stream;
3033

34+
import org.junit.jupiter.api.BeforeAll;
35+
import org.junit.jupiter.api.Test;
36+
import org.junit.jupiter.api.condition.EnabledOnOs;
37+
import org.junit.jupiter.api.condition.OS;
38+
import org.junit.jupiter.params.provider.Arguments;
39+
import org.junit.jupiter.params.ParameterizedTest;
40+
import org.junit.jupiter.params.provider.MethodSource;
41+
import static org.junit.jupiter.api.Assertions.*;
3142

3243
public class GetAbsolutePath {
3344

34-
private static boolean ignoreCase = false;
45+
private static final String USER_DIR = System.getProperty("user.dir");
3546

36-
private static void ck(String path, String ans) throws Exception {
37-
File f = new File(path);
38-
String p = f.getAbsolutePath();
39-
if ((ignoreCase && p.equalsIgnoreCase(ans)) || p.equals(ans))
40-
System.err.println(path + " ==> " + p);
41-
else
42-
throw new Exception(path + ": expected " + ans + ", got " + p);
47+
private static char driveLetter() {
48+
assert System.getProperty("os.name").startsWith("Windows");
49+
50+
if ((USER_DIR.length() > 2) && (USER_DIR.charAt(1) == ':')
51+
&& (USER_DIR.charAt(2) == '\\'))
52+
return USER_DIR.charAt(0);
53+
54+
throw new RuntimeException("Current directory has no drive");
4355
}
4456

45-
private static void testWin32() throws Exception {
46-
String wd = System.getProperty("user.dir");
47-
char d;
48-
if ((wd.length() > 2) && (wd.charAt(1) == ':')
49-
&& (wd.charAt(2) == '\\'))
50-
d = wd.charAt(0);
51-
else
52-
throw new Exception("Current directory has no drive");
53-
ck("/foo/bar", d + ":\\foo\\bar");
54-
ck("\\foo\\bar", d + ":\\foo\\bar");
55-
ck("c:\\foo\\bar", "c:\\foo\\bar");
56-
ck("c:/foo/bar", "c:\\foo\\bar");
57-
ck("\\\\foo\\bar", "\\\\foo\\bar");
57+
private static Stream<Arguments> windowsSource() {
58+
char drive = driveLetter();
59+
return Stream.of(Arguments.of("/foo/bar", drive + ":\\foo\\bar"),
60+
Arguments.of("\\foo\\bar", drive + ":\\foo\\bar"),
61+
Arguments.of("c:\\foo\\bar", "c:\\foo\\bar"),
62+
Arguments.of("c:/foo/bar", "c:\\foo\\bar"),
63+
Arguments.of("\\\\foo\\bar", "\\\\foo\\bar"),
64+
Arguments.of("", USER_DIR), // empty path
65+
Arguments.of("\\\\?\\foo", USER_DIR + "\\foo"),
66+
Arguments.of("\\\\?\\C:\\Users\\x", "C:\\Users\\x"),
67+
Arguments.of("\\\\?\\" + drive + ":", USER_DIR),
68+
Arguments.of("\\\\?\\" + drive + ":bar", USER_DIR + "\\bar"));
69+
}
70+
71+
@EnabledOnOs(OS.WINDOWS)
72+
@ParameterizedTest
73+
@MethodSource("windowsSource")
74+
public void windows(String path, String absolute) throws IOException {
75+
File file = new File(path);
76+
assertEquals(0, absolute.compareToIgnoreCase(file.getAbsolutePath()));
77+
}
5878

59-
/* Tricky directory-relative case */
60-
d = Character.toLowerCase(d);
79+
@EnabledOnOs(OS.WINDOWS)
80+
@Test
81+
public void windowsDriveRelative() throws IOException {
82+
// Tricky directory-relative case
83+
char d = Character.toLowerCase(driveLetter());
6184
char z = 0;
6285
if (d != 'c') z = 'c';
6386
else if (d != 'd') z = 'd';
6487
if (z != 0) {
6588
File f = new File(z + ":.");
6689
if (f.exists()) {
67-
String zwd = f.getCanonicalPath();
68-
ck(z + ":foo", zwd + "\\foo");
90+
String zUSER_DIR = f.getCanonicalPath();
91+
assertEquals(z + ":foo", zUSER_DIR + "\\foo");
6992
}
7093
}
71-
72-
/* Empty path */
73-
ck("", wd);
7494
}
7595

76-
private static void testUnix() throws Exception {
77-
String wd = System.getProperty("user.dir");
78-
ck("foo", wd + "/foo");
79-
ck("foo/bar", wd + "/foo/bar");
80-
ck("/foo", "/foo");
81-
ck("/foo/bar", "/foo/bar");
82-
83-
/* Empty path */
84-
ck("", wd);
96+
private static Stream<Arguments> unixSource() {
97+
return Stream.of(Arguments.of("foo", USER_DIR + "/foo"),
98+
Arguments.of("foo/bar", USER_DIR + "/foo/bar"),
99+
Arguments.of("/foo", "/foo"),
100+
Arguments.of("/foo/bar", "/foo/bar"),
101+
Arguments.of("", USER_DIR));
85102
}
86103

87-
public static void main(String[] args) throws Exception {
88-
if (File.separatorChar == '\\') {
89-
ignoreCase = true;
90-
testWin32();
91-
}
92-
if (File.separatorChar == '/') testUnix();
104+
@EnabledOnOs({OS.LINUX, OS.MAC})
105+
@ParameterizedTest
106+
@MethodSource("unixSource")
107+
public void unix(String path, String absolute) throws IOException {
108+
assertEquals(absolute, new File(path).getAbsolutePath());
93109
}
94-
95110
}

‎test/jdk/java/io/File/GetCanonicalPath.java

+71-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2003, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved.
33
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
44
*
55
* This code is free software; you can redistribute it and/or modify it
@@ -22,21 +22,81 @@
2222
*/
2323

2424
/* @test
25-
@bug 4899022
26-
@summary Look for erroneous representation of drive letter
25+
* @bug 4899022
26+
* @requires (os.family == "windows")
27+
* @summary Look for erroneous representation of drive letter
28+
* @run junit GetCanonicalPath
2729
*/
2830

29-
import java.io.*;
31+
import java.io.File;
32+
import java.io.IOException;
33+
import java.util.ArrayList;
34+
import java.util.List;
35+
import java.util.stream.Stream;
36+
37+
import org.junit.jupiter.api.Test;
38+
import org.junit.jupiter.params.ParameterizedTest;
39+
import org.junit.jupiter.params.provider.Arguments;
40+
import org.junit.jupiter.params.provider.MethodSource;
41+
import org.junit.jupiter.params.provider.ValueSource;
42+
43+
import static org.junit.jupiter.api.Assertions.*;
3044

3145
public class GetCanonicalPath {
32-
public static void main(String[] args) throws Exception {
33-
if (File.separatorChar == '\\') {
34-
testDriveLetter();
35-
}
46+
private static Stream<Arguments> pathProvider() {
47+
List<Arguments> list = new ArrayList<Arguments>();
48+
49+
File dir = new File(System.getProperty("user.dir", "."));
50+
char drive = dir.getPath().charAt(0);
51+
52+
String pathname = drive + ":\\";
53+
list.add(Arguments.of(pathname, pathname));
54+
55+
list.add(Arguments.of(drive + ":", dir.toString()));
56+
57+
String name = "foo";
58+
pathname = "\\\\?\\" + name;
59+
list.add(Arguments.of(pathname, new File(dir, name).toString()));
60+
pathname = "\\\\?\\" + drive + ":" + name;
61+
list.add(Arguments.of(pathname, new File(dir, name).toString()));
62+
63+
pathname = "foo\\bar\\gus";
64+
list.add(Arguments.of(pathname, new File(dir, pathname).toString()));
65+
66+
pathname = drive + ":\\foo\\bar\\gus";
67+
list.add(Arguments.of(pathname, pathname));
68+
69+
pathname = "\\\\server\\share\\foo\\bar\\gus";
70+
list.add(Arguments.of(pathname, pathname));
71+
72+
pathname = "\\\\localhost\\" + drive + "$\\Users\\file.dat";
73+
list.add(Arguments.of(pathname, pathname));
74+
75+
list.add(Arguments.of("\\\\?\\" + drive + ":\\Users\\file.dat",
76+
drive + ":\\Users\\file.dat"));
77+
list.add(Arguments.of("\\\\?\\UNC\\localhost\\" + drive + "$\\Users\\file.dat",
78+
"\\\\localhost\\" + drive + "$\\Users\\file.dat"));
79+
80+
return list.stream();
81+
}
82+
83+
@ParameterizedTest
84+
@ValueSource(strings = {"\\\\?", "\\\\?\\UNC", "\\\\?\\UNC\\"})
85+
void badPaths(String pathname) {
86+
assertThrows(IOException.class, () -> new File(pathname).getCanonicalPath());
87+
}
88+
89+
@ParameterizedTest
90+
@MethodSource("pathProvider")
91+
void goodPaths(String pathname, String expected) throws IOException {
92+
File file = new File(pathname);
93+
String canonicalPath = file.getCanonicalPath();
94+
assertEquals(expected, canonicalPath);
3695
}
37-
private static void testDriveLetter() throws Exception {
96+
97+
@Test
98+
void driveLetter() throws IOException {
3899
String path = new File("c:/").getCanonicalPath();
39-
if (path.length() > 3)
40-
throw new RuntimeException("Drive letter incorrectly represented");
100+
assertFalse(path.length() > 3, "Drive letter incorrectly represented");
41101
}
42102
}

0 commit comments

Comments
 (0)
Please sign in to comment.