8355342: File.getCanonicalPath on Java 24 resolves paths on network drives to UNC format

Reviewed-by: alanb
This commit is contained in:
Brian Burkhalter 2025-11-06 16:01:37 +00:00
parent 1321186547
commit 1f08a3ede2
4 changed files with 141 additions and 59 deletions

View File

@ -482,27 +482,12 @@ final class WinNTFileSystem extends FileSystem {
return path;
return "" + ((char) (c-32)) + ':' + '\\';
}
String canonicalPath = canonicalize0(path);
String finalPath = null;
try {
finalPath = getFinalPath(canonicalPath);
} catch (IOException ignored) {
finalPath = canonicalPath;
}
return finalPath;
return canonicalize0(path);
}
private native String canonicalize0(String path)
throws IOException;
private String getFinalPath(String path) throws IOException {
return getFinalPath0(path);
}
private native String getFinalPath0(String path)
throws IOException;
/* -- Attribute accessors -- */
@Override

View File

@ -59,7 +59,7 @@ Java_java_io_WinNTFileSystem_initIDs(JNIEnv *env, jclass cls)
/* -- Path operations -- */
extern int wcanonicalize(const WCHAR *path, WCHAR *out, int len);
extern WCHAR* wcanonicalize(const WCHAR *path, WCHAR *out, int len);
/**
* Retrieves the fully resolved (final) path for the given path or NULL
@ -274,18 +274,23 @@ Java_java_io_WinNTFileSystem_canonicalize0(JNIEnv *env, jobject this,
*/
int len = (int)wcslen(path);
len += currentDirLength(path, len);
WCHAR* fp;
if (len > MAX_PATH_LENGTH - 1) {
WCHAR *cp = (WCHAR*)malloc(len * sizeof(WCHAR));
if (cp != NULL) {
if (wcanonicalize(path, cp, len) >= 0) {
rv = (*env)->NewString(env, cp, (jsize)wcslen(cp));
if ((fp = wcanonicalize(path, cp, len)) != NULL) {
rv = (*env)->NewString(env, fp, (jsize)wcslen(fp));
if (fp != cp)
free(fp);
}
free(cp);
} else {
JNU_ThrowOutOfMemoryError(env, "native memory allocation failed");
}
} else if (wcanonicalize(path, canonicalPath, MAX_PATH_LENGTH) >= 0) {
rv = (*env)->NewString(env, canonicalPath, (jsize)wcslen(canonicalPath));
} else if ((fp = wcanonicalize(path, canonicalPath, MAX_PATH_LENGTH)) != NULL) {
rv = (*env)->NewString(env, fp, (jsize)wcslen(fp));
if (fp != canonicalPath)
free(fp);
}
} END_UNICODE_STRING(env, path);
if (rv == NULL && !(*env)->ExceptionCheck(env)) {
@ -294,26 +299,6 @@ Java_java_io_WinNTFileSystem_canonicalize0(JNIEnv *env, jobject this,
return rv;
}
JNIEXPORT jstring JNICALL
Java_java_io_WinNTFileSystem_getFinalPath0(JNIEnv* env, jobject this, jstring pathname) {
jstring rv = NULL;
WITH_UNICODE_STRING(env, pathname, path) {
WCHAR* finalPath = getFinalPath(env, path);
if (finalPath != NULL) {
rv = (*env)->NewString(env, finalPath, (jsize)wcslen(finalPath));
free(finalPath);
}
} END_UNICODE_STRING(env, path);
if (rv == NULL && !(*env)->ExceptionCheck(env)) {
JNU_ThrowIOExceptionWithLastError(env, "Bad pathname");
}
return rv;
}
/* -- Attribute accessors -- */
/* Check whether or not the file name in "path" is a Windows reserved

View File

@ -36,6 +36,7 @@
#include <windows.h>
#include <winbase.h>
#include <wchar.h>
#include <errno.h>
/* We should also include jdk_util.h here, for the prototype of JDK_Canonicalize.
@ -82,9 +83,9 @@ wnextsep(WCHAR *start)
/* Tell whether the given string contains any wildcard characters */
static int
wwild(WCHAR *start)
wwild(const WCHAR *start)
{
WCHAR *p = start;
WCHAR *p = (WCHAR*)start;
int c;
while (c = *p) {
if ((c == L'*') || (c == L'?'))
@ -146,12 +147,59 @@ lastErrorReportable()
return 1;
}
/* Convert a pathname to canonical form. The input orig_path is assumed to
have been converted to native form already, via JVM_NativePath(). This is
necessary because _fullpath() rejects duplicate separator characters on
Win95, though it accepts them on NT. */
int
wcanonicalize(WCHAR *orig_path, WCHAR *result, int size)
//
// Return the final path of 'path'. If 'finalPath' is long enough, the final
// path is placed in it. If not, a new character array is allocated for the
// return value. If the return value does not equal the original 'finalPath'
// value, then the calling code might need to free the memory of the
// parameter. Non-NULL is returned on success, NULL on error.
//
WCHAR* getFinalPath(WCHAR* path, WCHAR* finalPath, DWORD size)
{
HANDLE h = CreateFileW(path,
FILE_READ_ATTRIBUTES,
FILE_SHARE_DELETE |
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS,
NULL);
if (h != INVALID_HANDLE_VALUE) {
DWORD len = GetFinalPathNameByHandleW(h, finalPath, size, 0);
if (len >= size) {
if ((finalPath = (WCHAR*)malloc(len * sizeof(WCHAR))) == NULL)
return NULL;
len = GetFinalPathNameByHandleW(h, finalPath, len, 0);
}
CloseHandle(h);
if (len != 0) {
if (finalPath[0] == L'\\' && finalPath[1] == L'\\' &&
finalPath[2] == L'?' && finalPath[3] == L'\\')
{
// Strip prefix (should be \\?\ or \\?\UNC)
int isUnc = (finalPath[4] == L'U' &&
finalPath[5] == L'N' &&
finalPath[6] == L'C');
int prefixLen = (isUnc) ? 7 : 4;
// the amount to copy includes terminator
int amountToCopy = len - prefixLen + 1;
wmemmove(finalPath, finalPath + prefixLen, amountToCopy);
}
return finalPath;
}
}
return NULL;
}
/* Convert a pathname to canonical form. If a reparse point is encountered
while traversing the path, then the final path is derived from the full path
and returned as the canonical pathname.
*/
WCHAR*
wcanonicalize(const WCHAR *orig_path, WCHAR *result, int size)
{
WIN32_FIND_DATAW fd;
HANDLE h;
@ -161,11 +209,11 @@ wcanonicalize(WCHAR *orig_path, WCHAR *result, int size)
/* Reject paths that contain wildcards */
if (wwild(orig_path)) {
errno = EINVAL;
return -1;
return NULL;
}
if ((path = (WCHAR*)malloc(size * sizeof(WCHAR))) == NULL)
return -1;
return NULL;
/* Collapse instances of "foo\.." and ensure absoluteness. Note that
contrary to the documentation, the _fullpath procedure does not require
@ -237,6 +285,18 @@ wcanonicalize(WCHAR *orig_path, WCHAR *result, int size)
if (h != INVALID_HANDLE_VALUE) {
/* Lookup succeeded; append true name to result and continue */
FindClose(h);
// If a reparse point is encountered, get the final path.
if ((fd.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) != 0) {
// Do not fail if the final path cannot be obtained as the
// canonicalization may still be otherwise correct
WCHAR* fp = NULL;
if ((fp = getFinalPath(path, result, size)) != NULL) {
free(path);
return fp;
}
}
if (!(dst = wcp(dst, dend, L'\\', fd.cFileName,
fd.cFileName + wcslen(fd.cFileName)))){
goto err;
@ -261,11 +321,11 @@ wcanonicalize(WCHAR *orig_path, WCHAR *result, int size)
}
*dst = L'\0';
free(path);
return 0;
return result;
err:
free(path);
return -1;
return NULL;
}
/* Non-Wide character version of canonicalize.
@ -274,6 +334,7 @@ JNIEXPORT int
JDK_Canonicalize(const char *orig, char *out, int len) {
wchar_t* wpath = NULL;
wchar_t* wresult = NULL;
wchar_t* wcanon = NULL;
int wpath_len;
int ret = -1;
@ -297,12 +358,12 @@ JDK_Canonicalize(const char *orig, char *out, int len) {
goto finish;
}
if (wcanonicalize(wpath, wresult, len) != 0) {
if ((wcanon = wcanonicalize(wpath, wresult, len)) == NULL) {
goto finish;
}
if (WideCharToMultiByte(CP_ACP, 0,
wresult, -1, out, len, NULL, NULL) == 0) {
wcanon, -1, out, len, NULL, NULL) == 0) {
goto finish;
}
@ -310,8 +371,12 @@ JDK_Canonicalize(const char *orig, char *out, int len) {
ret = 0;
finish:
free(wresult);
free(wpath);
if (wcanon != NULL && wcanon != wresult)
free(wcanon);
if (wresult != NULL)
free(wresult);
if (wpath != NULL)
free(wpath);
return ret;
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2003, 2024, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2003, 2025, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -22,7 +22,7 @@
*/
/* @test
* @bug 4899022 8003887
* @bug 4899022 8003887 8355342
* @summary Look for erroneous representation of drive letter
* @run junit GetCanonicalPath
*/
@ -33,6 +33,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;
import org.junit.jupiter.api.Assumptions;
@ -127,6 +128,52 @@ public class GetCanonicalPath {
assertFalse(path.length() > 3, "Drive letter incorrectly represented");
}
@Test
@EnabledOnOs(OS.WINDOWS)
void mappedDrive() throws IOException {
// find the first unused drive letter
char drive = '[';
var roots = Set.of(new File(".").listRoots());
for (int i = 4; i < 26; i++) {
char c = (char)('A' + i);
if (!roots.contains(new File(c + ":\\"))) {
drive = c;
break;
}
}
assertFalse(drive == '['); // '[' is next after 'Z'
// map the first unused drive letter to the cwd
String cwd = System.getProperty("user.dir");
Runtime rt = Runtime.getRuntime();
String share =
"\\\\localhost\\" + cwd.charAt(0) + "$" + cwd.substring(2);
try {
Process p = rt.exec(new String[] {"net", "use", drive + ":", share});
assertEquals(0, p.waitFor());
} catch (InterruptedException x) {
fail(x);
}
// check that the canonical path name and its content are as expected
try {
final String filename = "file.txt";
final String text = "This is some text";
Files.writeString(Path.of(share, filename), text);
File file = new File(drive + ":\\" + filename);
String canonicalPath = file.getCanonicalPath();
assertEquals(drive + ":\\" + filename, canonicalPath);
assertEquals(text, Files.readString(Path.of(canonicalPath)));
} finally {
try {
Process p = rt.exec(new String[] {"net", "use", drive + ":", "/Delete"});
assertEquals(0, p.waitFor());
} catch (InterruptedException x) {
fail(x);
}
}
}
// Create a File with the given pathname and return the File as a Path
private static Path createFile(String pathname) throws IOException {
File file = new File(pathname);