--- old/src/share/classes/java/io/FileSystem.java 2013-02-15 20:20:22.452712690 -0800 +++ new/src/share/classes/java/io/FileSystem.java 2013-02-15 20:20:22.252712696 -0800 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2013, 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 @@ -237,6 +237,9 @@ } } + // NUL character + static final char CHAR_NUL = '\u0000'; + static { useCanonCaches = getBooleanProperty("sun.io.useCanonCaches", useCanonCaches); --- old/src/solaris/classes/java/io/UnixFileSystem.java 2013-02-15 20:20:23.424712663 -0800 +++ new/src/solaris/classes/java/io/UnixFileSystem.java 2013-02-15 20:20:23.060712672 -0800 @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2010, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2013, 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 @@ -55,42 +55,30 @@ return colon; } - /* A normal Unix pathname contains no duplicate slashes and does not end - with a slash. It may be the empty string. */ - - /* Normalize the given pathname, whose length is len, starting at the given - offset; everything before this offset is already normal. */ - private String normalize(String pathname, int len, int off) { - if (len == 0) return pathname; - int n = len; - while ((n > 0) && (pathname.charAt(n - 1) == '/')) n--; - if (n == 0) return "/"; - StringBuffer sb = new StringBuffer(pathname.length()); - if (off > 0) sb.append(pathname.substring(0, off)); + /* + * A normal Unix path name contains no duplicate slashes and does not end + * with a slash. It may be the empty string. Check and normalize the + * given pathname. The whole pathname string is only iterated once. + */ + @Override + public String normalize(String pathname) { + final int len = pathname.length(); + StringBuilder sb = new StringBuilder(len); char prevChar = 0; - for (int i = off; i < n; i++) { + + for (int i = 0; i < len; i++) { char c = pathname.charAt(i); + if (c == CHAR_NUL) continue; if ((prevChar == '/') && (c == '/')) continue; + sb.append(c); prevChar = c; } - return sb.toString(); - } - /* Check that the given pathname is normal. If not, invoke the real - normalizer on the part of the pathname that requires normalization. - This way we iterate through the whole pathname string only once. */ - public String normalize(String pathname) { - int n = pathname.length(); - char prevChar = 0; - for (int i = 0; i < n; i++) { - char c = pathname.charAt(i); - if ((prevChar == '/') && (c == '/')) - return normalize(pathname, n, i - 1); - prevChar = c; - } - if (prevChar == '/') return normalize(pathname, n, n - 1); - return pathname; + if (prevChar == '/' && sb.length() > 1) + sb.setLength(sb.length() - 1); + + return sb.toString(); } public int prefixLength(String pathname) { --- old/src/windows/classes/java/io/WinNTFileSystem.java 2013-02-15 20:20:24.340712635 -0800 +++ new/src/windows/classes/java/io/WinNTFileSystem.java 2013-02-15 20:20:24.128712642 -0800 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2012, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2013, 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 @@ -74,95 +74,113 @@ return semicolon; } - /* Check that the given pathname is normal. If not, invoke the real - normalizer on the part of the pathname that requires normalization. - This way we iterate through the whole pathname string only once. */ + /* + * Check whether the given path name is normal. If not, invoke the real + * normalizer on the part of the path name that requires normalization. + * The following logic iterates the whole path name string only once. + */ @Override public String normalize(String path) { - int n = path.length(); - char slash = this.slash; - char altSlash = this.altSlash; + final int len = path.length(); + if (len == 0) return path; + + StringBuilder sb = new StringBuilder(len); + int sbLen = 0; char prev = 0; - for (int i = 0; i < n; i++) { + int prevIdx = -1; + + for (int i = 0; i < len; i++) { char c = path.charAt(i); - if (c == altSlash) - return normalize(path, n, (prev == slash) ? i - 1 : i); - if ((c == slash) && (prev == slash) && (i > 1)) - return normalize(path, n, i - 1); - if ((c == ':') && (i > 1)) - return normalize(path, n, 0); + if (c == CHAR_NUL) continue; + + // Force to the preferred slash + if (c == altSlash) c = slash; + + sbLen = sb.length(); + if ((c == slash) && (prev == slash) && (sbLen > 1)) { + sb.setLength(sbLen - 1); + return normalize(path, prevIdx, sb); + } + + // If a path starts with a disk designator, like "c:", no + // normalization is needed. But if a disk designator is not at + // the beginning, normalization will be triggered to normalize + // the prefix. StringBuilder, sb, will be cleared in the real + // normalizer. + if ((c == ':') && isLetter(prev) && (sbLen > 1)) + return normalize(path, 0, sb); + + sb.append(c); prev = c; + prevIdx = i; + } + + sbLen = sb.length(); + if (prev == slash && sbLen > 1) { + sb.setLength(sbLen - 1); + return normalize(path, prevIdx, sb); } - if (prev == slash) return normalize(path, n, n - 1); - return path; + + return sb.toString(); } - /* Normalize the given pathname, whose length is len, starting at the given - offset; everything before this offset is already normal. */ - private String normalize(String path, int len, int off) { - if (len == 0) return path; - if (off < 3) off = 0; /* Avoid fencepost cases with UNC pathnames */ - int src; - char slash = this.slash; - StringBuffer sb = new StringBuffer(len); + /* + * Normalize a path name, starting at the given offset. + * Everything before this offset is already normal. + */ + private String normalize(String path, int off, StringBuilder sb) { + final int len = path.length(); + /* Avoid fencepost cases with UNC pathnames */ + int src = sb.length() < 3 ? 0 : off; - if (off == 0) { + if (src == 0) { /* Complete normalization, including prefix */ - src = normalizePrefix(path, len, sb); - } else { - /* Partial normalization */ - src = off; - sb.append(path.substring(0, off)); + sb.setLength(0); + src = normalizePrefix(path, sb); } - /* Remove redundant slashes from the remainder of the path, forcing all + /* Partial normalization + Remove redundant slashes from the remainder of the path, and force all slashes into the preferred slash */ + char prev = 0; + char c; while (src < len) { - char c = path.charAt(src++); + c = path.charAt(src++); + if (c == CHAR_NUL) continue; if (isSlash(c)) { - while ((src < len) && isSlash(path.charAt(src))) src++; - if (src == len) { - /* Check for trailing separator */ - int sn = sb.length(); - if ((sn == 2) && (sb.charAt(1) == ':')) { - /* "z:\\" */ - sb.append(slash); - break; - } - if (sn == 0) { - /* "\\" */ - sb.append(slash); - break; - } - if ((sn == 1) && (isSlash(sb.charAt(0)))) { - /* "\\\\" is not collapsed to "\\" because "\\\\" marks - the beginning of a UNC pathname. Even though it is - not, by itself, a valid UNC pathname, we leave it as - is in order to be consistent with the win32 APIs, - which treat this case as an invalid UNC pathname - rather than as an alias for the root directory of - the current drive. */ - sb.append(slash); - break; - } - /* Path does not denote a root directory, so do not append - trailing slash */ - break; - } else { - sb.append(slash); - } - } else { - sb.append(c); + if (prev == slash) + continue; + else if (c == altSlash) + c = slash; + } + + sb.append(c); + prev = c; + } + + /* Handle the trailing slash + * Note, "\\\\" is not collapsed to "\\" because "\\\\" marks the + * beginning of a UNC pathname. Even though it is not, by itself, a + * valid UNC pathname, we leave it as is in order to be consistent with + * the win32 APIs, which treat this case as an invalid UNC pathname + * rather than as an alias for the root directory of the current drive. + */ + if (prev == slash) { + int sbLen = sb.length(); + if (!(sbLen == 1 || + (sbLen == 2 && sb.charAt(0) == slash) || + (sbLen == 3 && isLetter(sb.charAt(0)) && sb.charAt(1) == ':'))) + { + sb.setLength(sbLen - 1); } } - String rv = sb.toString(); - return rv; + return sb.toString(); } /* A normal Win32 pathname contains no duplicate slashes, except possibly for a UNC prefix, and does not end with a slash. It may be the empty - string. Normalized Win32 pathnames have the convenient property that + string. Normalized Win32 path names have the convenient property that the length of the prefix almost uniquely identifies the type of the path and whether it is absolute or relative: @@ -172,35 +190,51 @@ else directory-relative (has form "z:foo") 3 absolute local pathname (begins with "z:\\") */ - private int normalizePrefix(String path, int len, StringBuffer sb) { - int src = 0; - while ((src < len) && isSlash(path.charAt(src))) src++; + private int normalizePrefix(String path, StringBuilder sb) { + final int len = path.length(); + // Remove leading NUL chars + int start = -1; + while (++start < len && path.charAt(start) == CHAR_NUL); + + // Normalize disk designator preifx + int src = start; + // Remove slashes and NUL chars + while (src < len && + (isSlash(path.charAt(src)) || path.charAt(src) == CHAR_NUL)) + { + src++; + } + char c; - if ((len - src >= 2) - && isLetter(c = path.charAt(src)) - && path.charAt(src + 1) == ':') { - /* Remove leading slashes if followed by drive specifier. - This hack is necessary to support file URLs containing drive - specifiers (e.g., "file://c:/path"). As a side effect, - "/c:/path" can be used as an alternative to "c:/path". */ - sb.append(c); - sb.append(':'); - src += 2; - } else { - src = 0; - if ((len >= 2) - && isSlash(path.charAt(0)) - && isSlash(path.charAt(1))) { + if ((len - src >= 2) && isLetter(c = path.charAt(src))) { + // Find the next non-NUL char + while (++src < len && path.charAt(src) == CHAR_NUL); + if (src < len && path.charAt(src) == ':') { + /* Remove leading slashes if followed by drive specifier. + This hack is necessary to support file URLs containing drive + specifiers (e.g., "file://c:/path"). As a side effect, + "/c:/path" can be used as an alternative to "c:/path". */ + sb.append(c); + sb.append(':'); + return src + 1; + } + } + + // Normalize UNC prefix + src = start; + if ((len - src >= 2) && isSlash(path.charAt(src))) { + while (++src < len && path.charAt(src) == CHAR_NUL); + if (src < len && isSlash(path.charAt(src))) { /* UNC pathname: Retain first slash; leave src pointed at second slash so that further slashes will be collapsed into the second slash. The result will be a pathname beginning with "\\\\" followed (most likely) by a host name. */ - src = 1; sb.append(slash); + return src; } } - return src; + return start; } @Override --- /dev/null 2013-02-15 17:07:12.992334364 -0800 +++ new/test/java/io/File/Normalize.java 2013-02-15 20:20:24.936712617 -0800 @@ -0,0 +1,288 @@ +/* + * Copyright (c) 2013, 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* @test + * @bug 8003992 + * @summary Test file path name normalizer, and ensure it can handle embedded + * NUL character correctly. + * @author Dan Xu + */ + +import java.io.File; + +public class Normalize { + + private final static char CHAR_NUL = '\u0000'; + + public static void main(String[] args) { + testNormalizer(); + testUnixNormalizer(); + testWindowsNormalizer(); + } + + private static void testNormalizer() { + StringBuilder name = new StringBuilder(); + + test(name.append(""), ""); + test(name.append("").append(CHAR_NUL), ""); + test(name.append(CHAR_NUL).append("").append(CHAR_NUL), ""); + test(name.append(CHAR_NUL).append(CHAR_NUL).append("") + .append(CHAR_NUL).append(CHAR_NUL).append("") + .append(CHAR_NUL).append(CHAR_NUL), ""); + } + + private static void testUnixNormalizer() { + String osName = System.getProperty("os.name"); + if (osName.startsWith("Windows")) + return; + + StringBuilder name = new StringBuilder(); + + test(name.append('/'), "/"); + + name.setLength(0); + test(name.append(CHAR_NUL).append("/").append(CHAR_NUL), "/"); + + name.setLength(0); + test(name.append("//"), "/"); + + name.setLength(0); + test(name.append(CHAR_NUL).append("/").append(CHAR_NUL) + .append("/").append(CHAR_NUL), "/"); + + name.setLength(0); + test(name.append("///"), "/"); + + name.setLength(0); + test(name.append(CHAR_NUL).append("/").append(CHAR_NUL) + .append("/").append(CHAR_NUL) + .append("/").append(CHAR_NUL), "/"); + + name.setLength(0); + test(name.append("/data//"), "/data"); + + name.setLength(0); + test(name.append("/data").append(CHAR_NUL).append('/') + .append(CHAR_NUL).append('/'), "/data"); + + name.setLength(0); + test(name.append("/data//info"), "/data/info"); + + name.setLength(0); + test(name.append("/data//info/"), "/data/info"); + + name.setLength(0); + test(name.append("//data///info//"), "/data/info"); + + name.setLength(0); + test(name.append(CHAR_NUL).append('/').append(CHAR_NUL).append('/') + .append("//data//").append(CHAR_NUL).append("//info//") + .append(CHAR_NUL).append("/").append(CHAR_NUL), "/data/info"); + + name.setLength(0); + test(name.append("//data///info//Book///"), "/data/info/Book"); + + name.setLength(0); + test(name.append(CHAR_NUL).append('/').append(CHAR_NUL).append('/') + .append("//data//").append(CHAR_NUL).append("//info//") + .append(CHAR_NUL).append("/").append(CHAR_NUL).append("Book") + .append(CHAR_NUL).append("///"), "/data/info/Book"); + + name.setLength(0); + test(name.append("data///info//Book///"), "data/info/Book"); + + name.setLength(0); + test(name.append(CHAR_NUL).append(CHAR_NUL).append("data//") + .append(CHAR_NUL).append("//info//").append(CHAR_NUL) + .append("/").append(CHAR_NUL).append("Book") + .append(CHAR_NUL).append("///"), "data/info/Book"); + } + + private static void testWindowsNormalizer() { + String osName = System.getProperty("os.name"); + if (!osName.startsWith("Windows")) + return; + + StringBuilder name = new StringBuilder(); + + test(name.append('/'), "\\"); + + name.setLength(0); + test(name.append(CHAR_NUL).append("/").append(CHAR_NUL), "\\"); + + name.setLength(0); + test(name.append('\\'), "\\"); + + name.setLength(0); + test(name.append(CHAR_NUL).append("\\").append(CHAR_NUL), "\\"); + + name.setLength(0); + test(name.append("\\\\"), "\\\\"); + + name.setLength(0); + test(name.append(CHAR_NUL).append("\\").append(CHAR_NUL) + .append("\\").append(CHAR_NUL), "\\\\"); + + name.setLength(0); + test(name.append("\\/"), "\\\\"); + + name.setLength(0); + test(name.append(CHAR_NUL).append("\\").append(CHAR_NUL) + .append("/").append(CHAR_NUL), "\\\\"); + + name.setLength(0); + test(name.append("/\\"), "\\\\"); + + name.setLength(0); + test(name.append(CHAR_NUL).append(CHAR_NUL).append("/").append(CHAR_NUL) + .append("\\").append(CHAR_NUL), "\\\\"); + + name.setLength(0); + test(name.append("/\\/"), "\\\\"); + + name.setLength(0); + test(name.append("/\\\\"), "\\\\"); + + name.setLength(0); + test(name.append("\\\\\\"), "\\\\"); + + name.setLength(0); + test(name.append("///"), "\\\\"); + + name.setLength(0); + test(name.append(CHAR_NUL).append(CHAR_NUL).append("/").append(CHAR_NUL) + .append("\\").append(CHAR_NUL) + .append("\\").append(CHAR_NUL), "\\\\"); + + name.setLength(0); + test(name.append("z:"), "z:"); + + name.setLength(0); + test(name.append(CHAR_NUL).append("z:").append(CHAR_NUL), "z:"); + + name.setLength(0); + test(name.append("\\z:"), "z:"); + + name.setLength(0); + test(name.append(CHAR_NUL).append("\\").append(CHAR_NUL) + .append("z:").append(CHAR_NUL), "z:"); + + name.setLength(0); + test(name.append("z:\\"), "z:\\"); + + name.setLength(0); + test(name.append(CHAR_NUL).append(CHAR_NUL) + .append("z:").append(CHAR_NUL).append("\\"), "z:\\"); + + name.setLength(0); + test(name.append(CHAR_NUL).append(CHAR_NUL) + .append("z:").append("\\").append(CHAR_NUL), "z:\\"); + + name.setLength(0); + test(name.append("z:/"), "z:\\"); + + name.setLength(0); + test(name.append(CHAR_NUL).append(CHAR_NUL) + .append("z:").append(CHAR_NUL).append("/"), "z:\\"); + + name.setLength(0); + test(name.append(CHAR_NUL).append(CHAR_NUL).append("z:") + .append("/").append(CHAR_NUL).append(CHAR_NUL), "z:\\"); + + name.setLength(0); + test(name.append("\\z:\\"), "z:\\"); + + name.setLength(0); + test(name.append(CHAR_NUL).append("\\").append("z").append(CHAR_NUL) + .append(':').append(CHAR_NUL).append("\\") + .append(CHAR_NUL), "z:\\"); + + name.setLength(0); + test(name.append("\\z:\\\\"), "z:\\"); + + name.setLength(0); + test(name.append(CHAR_NUL).append("\\").append("z").append(CHAR_NUL) + .append(':').append(CHAR_NUL).append("\\") + .append(CHAR_NUL).append('/'), "z:\\"); + + name.setLength(0); + test(name.append("\\\\z:\\\\"), "z:\\"); + + name.setLength(0); + test(name.append('/').append(CHAR_NUL).append("\\").append("z") + .append(CHAR_NUL).append(':').append(CHAR_NUL).append("\\") + .append(CHAR_NUL).append('/').append(CHAR_NUL), "z:\\"); + + name.setLength(0); + test(name.append("\\\\\\z:\\\\\\"), "z:\\"); + + name.setLength(0); + test(name.append(CHAR_NUL).append("\\").append('/').append(CHAR_NUL) + .append("\\").append("z").append(CHAR_NUL).append(CHAR_NUL) + .append(':').append(CHAR_NUL).append("\\").append(CHAR_NUL) + .append('/').append(CHAR_NUL).append(CHAR_NUL) + .append('/').append(CHAR_NUL).append(CHAR_NUL), "z:\\"); + + name.setLength(0); + test(name.append("\\\\\\//z:\\\\\\"), "z:\\"); + + name.setLength(0); + test(name.append(CHAR_NUL).append("\\").append('/').append(CHAR_NUL) + .append("\\").append("//").append(CHAR_NUL).append("z") + .append(CHAR_NUL).append(CHAR_NUL).append(CHAR_NUL) + .append(':').append(CHAR_NUL).append("\\").append(CHAR_NUL) + .append('/').append(CHAR_NUL).append(CHAR_NUL) + .append('/').append(CHAR_NUL).append(CHAR_NUL), "z:\\"); + + name.setLength(0); + test(name.append("\\\\\\//z:\\\\\\data///info/\\Book"), + "z:\\data\\info\\Book"); + + name.setLength(0); + test(name.append(CHAR_NUL).append("\\").append('/').append(CHAR_NUL) + .append("\\").append("//").append(CHAR_NUL).append("z") + .append(CHAR_NUL).append(CHAR_NUL).append(CHAR_NUL) + .append(':').append(CHAR_NUL).append("\\").append(CHAR_NUL) + .append('/').append(CHAR_NUL).append(CHAR_NUL) + .append('/').append(CHAR_NUL).append(CHAR_NUL) + .append("data").append('\\').append(CHAR_NUL) + .append('/').append(CHAR_NUL).append(CHAR_NUL) + .append("info").append('/').append(CHAR_NUL) + .append('\\').append(CHAR_NUL).append(CHAR_NUL) + .append("Book").append('/').append(CHAR_NUL) + .append('\\').append(CHAR_NUL).append(CHAR_NUL) + .append(CHAR_NUL), "z:\\data\\info\\Book"); + } + + private static void test(StringBuilder pathName, String normalPath) { + File aFile = new File(pathName.toString()); + String path = aFile.getPath(); + + if (!normalPath.equals(path)) + throw new RuntimeException("File path, " + pathName + + "(length " + pathName.length() + + "), should be normalized to, " + normalPath + + "(length " + normalPath.length() + + "), not, " + path + "(length " + path.length() + ")"); + } +}