001    /*
002     * Jpkg - Java library and tools for operating system package creation.
003     *
004     * Copyright (c) 2007 Three Rings Design, Inc.
005     * All rights reserved.
006     * Copyright (c) 2004, Regents of the University of California
007     *
008     * Redistribution and use in source and binary forms, with or without
009     * modification, are permitted provided that the following conditions
010     * are met:
011     * 1. Redistributions of source code must retain the above copyright
012     *    notice, this list of conditions and the following disclaimer.
013     * 2. Redistributions in binary form must reproduce the above copyright
014     *    notice, this list of conditions and the following disclaimer in the
015     *    documentation and/or other materials provided with the distribution.
016     * 3. Neither the name of the copyright owner nor the names of contributors
017     *    may be used to endorse or promote products derived from this software
018     *    without specific prior written permission.
019     *
020     * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
021     * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
022     * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
023     * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
024     * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
025     * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
026     * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
027     * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
028     * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
029     * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
030     * POSSIBILITY OF SUCH DAMAGE.
031     */
032    package com.threerings.jpkg;
033    
034    import java.io.File;
035    
036    /**
037     * Small utility class for working with file paths.
038     */
039    public class PathUtils
040    {
041        /**
042         * Normalize a path by removing all relative attributes, such as ./.. and stripping
043         * any separators from the start and end of the path.
044         */
045        public static String normalize (String path)
046        {
047            /* Create a buffer in which we can normalize the Path. */
048            final StringBuilder trimPath = new StringBuilder();
049    
050            /* Remove any leading or trailing whitespace from the Path. */
051            trimPath.append(path.trim());
052    
053            /* Determine the length of the Path. */
054            int len = trimPath.length();
055            int lastSlash = -1;
056            int lastLastSlash = -1;
057    
058            for (int i = 0; i < len; i++) {
059                assert len == trimPath.length();
060    
061                /* Remove any double slashes created by concatenating partial
062                 * normalized paths together. */
063                if (i < len - 1 &&
064                        trimPath.charAt(i) == '/' &&
065                        trimPath.charAt(i + 1) == '/')
066                {
067                    trimPath.deleteCharAt(i);
068                    len--;
069                    i--;
070                    continue;
071                }
072    
073                /* Remove ./ */
074                if (i < len - 1 &&
075                        i == lastSlash + 1 &&
076                        trimPath.charAt(i) == '.' &&
077                        trimPath.charAt(i + 1) == '/')
078                {
079                    trimPath.delete(i, i + 2);
080                    len -= 2;
081                }
082    
083                /* Remove xxx/../ and ../../[xxx] */
084                if (i < len - 2 &&
085                        i == lastSlash + 1 &&
086                        trimPath.charAt(i) == '.' &&
087                        trimPath.charAt(i + 1) == '.' &&
088                        trimPath.charAt(i + 2) == '/')
089                {
090                    if (lastLastSlash >= 0) {
091                        trimPath.delete(lastLastSlash, i + 2);
092                        len -= (i + 2 - lastLastSlash);
093                        i = lastLastSlash;
094                        lastSlash = trimPath.lastIndexOf("/", lastLastSlash - 1);
095                    } else {
096                        /* First ../ at start of path. Attempted traversal beyond
097                         * the path -- we strip off the '..' and leave '/' */
098                        trimPath.delete(i, i + 2);
099                        len -= (i + 2);
100                        lastSlash = 0;
101                        lastLastSlash = 0;
102                    }
103                }
104    
105                if (trimPath.charAt(i) == '/') {
106                    lastLastSlash = lastSlash;
107                    lastSlash = i;
108                }
109            }
110            assert len == trimPath.length();
111    
112            /* If the normalized Path is empty, return an empty string. */
113            if (len == 0) {
114                return "";
115            }
116    
117            /* Remove any trailing '/' if it exists. */
118            if (trimPath.charAt(len - 1) == '/') {
119                trimPath.deleteCharAt(len - 1);
120            }
121    
122            /* Return the resulting normalized Path. */
123            return trimPath.toString();
124        }
125    
126        /**
127         * Strip any leading path separators from the start of the path.
128         */
129        public static String stripLeadingSeparators (String path) {
130            if (path.length() == 0) {
131                return path;
132            }
133    
134            if (path.charAt(0) != File.separatorChar) {
135                return path;
136            }
137    
138            int ii;
139            for (ii = 0; ii < path.length(); ii++) {
140                if (path.charAt(ii) != File.separatorChar) {
141                    break;
142                }
143            }
144    
145            return path.substring(ii);
146        }
147    }