1 /* 2 * Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.nio.fs; 27 28 import java.nio.file.InvalidPathException; 29 30 /** 31 * A parser of Windows path strings 32 */ 33 34 class WindowsPathParser { 35 private WindowsPathParser() { } 36 37 /** 38 * The result of a parse operation 39 */ 40 static class Result { 41 private final WindowsPathType type; 42 private final String root; 43 private final String path; 44 45 Result(WindowsPathType type, String root, String path) { 46 this.type = type; 47 this.root = root; 48 this.path = path; 49 } 50 51 /** 52 * The path type 53 */ 54 WindowsPathType type() { 55 return type; 56 } 57 58 /** 59 * The root component 60 */ 61 String root() { 62 return root; 63 } 64 65 /** 66 * The normalized path (includes root) 67 */ 68 String path() { 69 return path; 70 } 71 } 72 73 /** 74 * Parses the given input as a Windows path 75 */ 76 static Result parse(String input) { 77 return parse(input, true); 78 } 79 80 /** 81 * Parses the given input as a Windows path where it is known that the 82 * path is already normalized. 83 */ 84 static Result parseNormalizedPath(String input) { 85 return parse(input, false); 86 } 87 88 /** 89 * Parses the given input as a Windows path. 90 * 91 * @param requireToNormalize 92 * Indicates if the path requires to be normalized 93 */ 94 private static Result parse(String input, boolean requireToNormalize) { 95 String root = ""; 96 WindowsPathType type = null; 97 98 int len = input.length(); 99 int off = 0; 100 if (len > 1) { 101 char c0 = input.charAt(0); 102 char c1 = input.charAt(1); 103 char c = 0; 104 int next = 2; 105 if (isSlash(c0) && isSlash(c1)) { 106 // UNC: We keep the first two slash, collapse all the 107 // following, then take the hostname and share name out, 108 // meanwhile collapsing all the redundant slashes. 109 type = WindowsPathType.UNC; 110 off = nextNonSlash(input, next, len); 111 next = nextSlash(input, off, len); 112 if (off == next) 113 throw new InvalidPathException(input, "UNC path is missing hostname"); 114 String host = input.substring(off, next); //host 115 off = nextNonSlash(input, next, len); 116 next = nextSlash(input, off, len); 117 if (off == next) 118 throw new InvalidPathException(input, "UNC path is missing sharename"); 119 root = "\\\\" + host + "\\" + input.substring(off, next) + "\\"; 120 off = next; 121 } else { 122 if (isLetter(c0) && c1 == ':') { 123 root = input.substring(0, 2); 124 if (len > 2 && isSlash(input.charAt(2))) { 125 off = 3; 126 root += "\\"; 127 type = WindowsPathType.ABSOLUTE; 128 } else { 129 off = 2; 130 type = WindowsPathType.DRIVE_RELATIVE; 131 } 132 } 133 } 134 } 135 if (off == 0) { 136 if (len > 0 && isSlash(input.charAt(0))) { 137 type = WindowsPathType.DIRECTORY_RELATIVE; 138 root = "\\"; 139 } else { 140 type = WindowsPathType.RELATIVE; 141 } 142 } 143 144 if (requireToNormalize) { 145 StringBuilder sb = new StringBuilder(input.length()); 146 sb.append(root); 147 return new Result(type, root, normalize(sb, input, off)); 148 } else { 149 return new Result(type, root, input); 150 } 151 } 152 153 /** 154 * Remove redundant slashes from the rest of the path, forcing all slashes 155 * into the preferred slash. 156 */ 157 private static String normalize(StringBuilder sb, String path, int off) { 158 int len = path.length(); 159 off = nextNonSlash(path, off, len); 160 int start = off; 161 char lastC = 0; 162 while (off < len) { 163 char c = path.charAt(off); 164 if (isSlash(c)) { 165 if (lastC == ' ') 166 throw new InvalidPathException(path, 167 "Trailing char <" + lastC + ">", 168 off - 1); 169 sb.append(path, start, off); 170 off = nextNonSlash(path, off, len); 171 if (off != len) //no slash at the end of normalized path 172 sb.append('\\'); 173 start = off; 174 } else { 175 if (isInvalidPathChar(c)) 176 throw new InvalidPathException(path, 177 "Illegal char <" + c + ">", 178 off); 179 lastC = c; 180 off++; 181 } 182 } 183 if (start != off) { 184 if (lastC == ' ') 185 throw new InvalidPathException(path, 186 "Trailing char <" + lastC + ">", 187 off - 1); 188 sb.append(path, start, off); 189 } 190 return sb.toString(); 191 } 192 193 private static final boolean isSlash(char c) { 194 return (c == '\\') || (c == '/'); 195 } 196 197 private static final int nextNonSlash(String path, int off, int end) { 198 while (off < end && isSlash(path.charAt(off))) { off++; } 199 return off; 200 } 201 202 private static final int nextSlash(String path, int off, int end) { 203 char c; 204 while (off < end && !isSlash(c=path.charAt(off))) { 205 if (isInvalidPathChar(c)) 206 throw new InvalidPathException(path, 207 "Illegal character [" + c + "] in path", 208 off); 209 off++; 210 } 211 return off; 212 } 213 214 private static final boolean isLetter(char c) { 215 return ((c >= 'a') && (c <= 'z')) || ((c >= 'A') && (c <= 'Z')); 216 } 217 218 // Reserved characters for window path name 219 private static final String reservedChars = "<>:\"|?*"; 220 private static final boolean isInvalidPathChar(char ch) { 221 return ch < '\u0020' || reservedChars.indexOf(ch) != -1; 222 } 223 }