1 /*
   2  * Copyright (c) 2008, 2009, 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 }