mirror of
https://github.com/openjdk/jdk.git
synced 2026-01-28 12:09:14 +00:00
8355342: File.getCanonicalPath on Java 24 resolves paths on network drives to UNC format
Reviewed-by: alanb
This commit is contained in:
parent
1321186547
commit
1f08a3ede2
@ -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
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user