    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 * contributor license agreements.  See the NOTICE file distributed with
    004 * this work for additional information regarding copyright ownership.
    005 * The ASF licenses this file to You under the Apache License, Version 2.0
    006 * (the "License"); you may not use this file except in compliance with
    007 * the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 * Unless required by applicable law or agreed to in writing, software
    012 * distributed under the License is distributed on an "AS IS" BASIS,
    013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 * See the License for the specific language governing permissions and
    015 * limitations under the License.
    016 */
    017package org.apache.commons.io;
    018
    019import java.io.File;
    020import java.io.IOException;
    021import java.util.ArrayDeque;
    022import java.util.ArrayList;
    023import java.util.Arrays;
    024import java.util.Collection;
    025import java.util.Deque;
    026import java.util.List;
    027import java.util.Objects;
    028import java.util.regex.Matcher;
    029import java.util.regex.Pattern;
    030
    031/**
    032 * General file name and file path manipulation utilities.
    033 * <p>
    034 * When dealing with file names you can hit problems when moving from a Windows
    035 * based development machine to a Unix based production machine.
    036 * This class aims to help avoid those problems.
    037 * <p>
    038 * <b>NOTE</b>: You may be able to avoid using this class entirely simply by
    039 * using JDK {@link java.io.File File} objects and the two argument constructor
    040 * {@link java.io.File#File(java.io.File, java.lang.String) File(File,String)}.
    041 * <p>
    042 * Most methods on this class are designed to work the same on both Unix and Windows.
    043 * Those that don't include 'System', 'Unix' or 'Windows' in their name.
    044 * <p>
    045 * Most methods recognise both separators (forward and back), and both
    046 * sets of prefixes. See the javadoc of each method for details.
    047 * <p>
    048 * This class defines six components within a file name
    049 * (example C:\dev\project\file.txt):
    050 * <ul>
    051 * <li>the prefix - C:\</li>
    052 * <li>the path - dev\project\</li>
    053 * <li>the full path - C:\dev\project\</li>
    054 * <li>the name - file.txt</li>
    055 * <li>the base name - file</li>
    056 * <li>the extension - txt</li>
    057 * </ul>
    058 * Note that this class works best if directory file names end with a separator.
    059 * If you omit the last separator, it is impossible to determine if the file name
    060 * corresponds to a file or a directory. As a result, we have chosen to say
    061 * it corresponds to a file.
    062 * <p>
    063 * This class only supports Unix and Windows style names.
    064 * Prefixes are matched as follows:
    065 * <pre>
    066 * Windows:
    067 * a\b\c.txt           --&gt; ""          --&gt; relative
    068 * \a\b\c.txt          --&gt; "\"         --&gt; current drive absolute
    069 * C:a\b\c.txt         --&gt; "C:"        --&gt; drive relative
    070 * C:\a\b\c.txt        --&gt; "C:\"       --&gt; absolute
    071 * \\server\a\b\c.txt  --&gt; "\\server\" --&gt; UNC
    072 *
    073 * Unix:
    074 * a/b/c.txt           --&gt; ""          --&gt; relative
    075 * /a/b/c.txt          --&gt; "/"         --&gt; absolute
    076 * ~/a/b/c.txt         --&gt; "~/"        --&gt; current user
    077 * ~                   --&gt; "~/"        --&gt; current user (slash added)
    078 * ~user/a/b/c.txt     --&gt; "~user/"    --&gt; named user
    079 * ~user               --&gt; "~user/"    --&gt; named user (slash added)
    080 * </pre>
    081 * Both prefix styles are matched always, irrespective of the machine that you are
    082 * currently running on.
    083 * <p>
    084 * Origin of code: Excalibur, Alexandria, Tomcat, Commons-Utils.
    085 *
    086 * @since 1.1
    087 */
    088public class FilenameUtils {
    089
    090    private static final String[] EMPTY_STRING_ARRAY = new String[0];
    091
    092    private static final String EMPTY_STRING = "";
    093
    094    private static final int NOT_FOUND = -1;
    095
    096    /**
    097     * The extension separator character.
    098     * @since 1.4
    099     */
    100    public static final char EXTENSION_SEPARATOR = '.';
    101
    102    /**
    103     * The extension separator String.
    104     * @since 1.4
    105     */
    106    public static final String EXTENSION_SEPARATOR_STR = Character.toString(EXTENSION_SEPARATOR);
    107
    108    /**
    109     * The Unix separator character.
    110     */
    111    private static final char UNIX_SEPARATOR = '/';
    112
    113    /**
    114     * The Windows separator character.
    115     */
    116    private static final char WINDOWS_SEPARATOR = '\\';
    117
    118    /**
    119     * The system separator character.
    120     */
    121    private static final char SYSTEM_SEPARATOR = File.separatorChar;
    122
    123    /**
    124     * The separator character that is the opposite of the system separator.
    125     */
    126    private static final char OTHER_SEPARATOR;
    127    static {
    128        if (isSystemWindows()) {
    129            OTHER_SEPARATOR = UNIX_SEPARATOR;
    130        } else {
    131            OTHER_SEPARATOR = WINDOWS_SEPARATOR;
    132        }
    133    }
    134
    135    /**
    136     * Instances should NOT be constructed in standard programming.
    137     */
    138    public FilenameUtils() {
    139        super();
    140    }
    141
    142    //-----------------------------------------------------------------------
    143    /**
    144     * Determines if Windows file system is in use.
    145     *
    146     * @return true if the system is Windows
    147     */
    148    static boolean isSystemWindows() {
    149        return SYSTEM_SEPARATOR == WINDOWS_SEPARATOR;
    150    }
    151
    152    //-----------------------------------------------------------------------
    153    /**
    154     * Checks if the character is a separator.
    155     *
    156     * @param ch  the character to check
    157     * @return true if it is a separator character
    158     */
    159    private static boolean isSeparator(final char ch) {
    160        return ch == UNIX_SEPARATOR || ch == WINDOWS_SEPARATOR;
    161    }
    162
    163    //-----------------------------------------------------------------------
    164    /**
    165     * Normalizes a path, removing double and single dot path steps.
    166     * <p>
    167     * This method normalizes a path to a standard format.
    168     * The input may contain separators in either Unix or Windows format.
    169     * The output will contain separators in the format of the system.
    170     * <p>
    171     * A trailing slash will be retained.
    172     * A double slash will be merged to a single slash (but UNC names are handled).
    173     * A single dot path segment will be removed.
    174     * A double dot will cause that path segment and the one before to be removed.
    175     * If the double dot has no parent path segment to work with, {@code null}
    176     * is returned.
    177     * <p>
    178     * The output will be the same on both Unix and Windows except
    179     * for the separator character.
    180     * <pre>
    181     * /foo//               --&gt;   /foo/
    182     * /foo/./              --&gt;   /foo/
    183     * /foo/../bar          --&gt;   /bar
    184     * /foo/../bar/         --&gt;   /bar/
    185     * /foo/../bar/../baz   --&gt;   /baz
    186     * //foo//./bar         --&gt;   /foo/bar
    187     * /../                 --&gt;   null
    188     * ../foo               --&gt;   null
    189     * foo/bar/..           --&gt;   foo/
    190     * foo/../../bar        --&gt;   null
    191     * foo/../bar           --&gt;   bar
    192     * //server/foo/../bar  --&gt;   //server/bar
    193     * //server/../bar      --&gt;   null
    194     * C:\foo\..\bar        --&gt;   C:\bar
    195     * C:\..\bar            --&gt;   null
    196     * ~/foo/../bar/        --&gt;   ~/bar/
    197     * ~/../bar             --&gt;   null
    198     * </pre>
    199     * (Note the file separator returned will be correct for Windows/Unix)
    200     *
    201     * @param fileName  the fileName to normalize, null returns null
    202     * @return the normalized fileName, or null if invalid. Null bytes inside string will be removed
    203     */
    204    public static String normalize(final String fileName) {
    205        return doNormalize(fileName, SYSTEM_SEPARATOR, true);
    206    }
    207    /**
    208     * Normalizes a path, removing double and single dot path steps.
    209     * <p>
    210     * This method normalizes a path to a standard format.
    211     * The input may contain separators in either Unix or Windows format.
    212     * The output will contain separators in the format specified.
    213     * <p>
    214     * A trailing slash will be retained.
    215     * A double slash will be merged to a single slash (but UNC names are handled).
    216     * A single dot path segment will be removed.
    217     * A double dot will cause that path segment and the one before to be removed.
    218     * If the double dot has no parent path segment to work with, {@code null}
    219     * is returned.
    220     * <p>
    221     * The output will be the same on both Unix and Windows except
    222     * for the separator character.
    223     * <pre>
    224     * /foo//               --&gt;   /foo/
    225     * /foo/./              --&gt;   /foo/
    226     * /foo/../bar          --&gt;   /bar
    227     * /foo/../bar/         --&gt;   /bar/
    228     * /foo/../bar/../baz   --&gt;   /baz
    229     * //foo//./bar         --&gt;   /foo/bar
    230     * /../                 --&gt;   null
    231     * ../foo               --&gt;   null
    232     * foo/bar/..           --&gt;   foo/
    233     * foo/../../bar        --&gt;   null
    234     * foo/../bar           --&gt;   bar
    235     * //server/foo/../bar  --&gt;   //server/bar
    236     * //server/../bar      --&gt;   null
    237     * C:\foo\..\bar        --&gt;   C:\bar
    238     * C:\..\bar            --&gt;   null
    239     * ~/foo/../bar/        --&gt;   ~/bar/
    240     * ~/../bar             --&gt;   null
    241     * </pre>
    242     * The output will be the same on both Unix and Windows including
    243     * the separator character.
    244     *
    245     * @param fileName  the fileName to normalize, null returns null
    246     * @param unixSeparator {@code true} if a unix separator should
    247     * be used or {@code false} if a windows separator should be used.
    248     * @return the normalized fileName, or null if invalid. Null bytes inside string will be removed
    249     * @since 2.0
    250     */
    251    public static String normalize(final String fileName, final boolean unixSeparator) {
    252        final char separator = unixSeparator ? UNIX_SEPARATOR : WINDOWS_SEPARATOR;
    253        return doNormalize(fileName, separator, true);
    254    }
    255
    256    //-----------------------------------------------------------------------
    257    /**
    258     * Normalizes a path, removing double and single dot path steps,
    259     * and removing any final directory separator.
    260     * <p>
    261     * This method normalizes a path to a standard format.
    262     * The input may contain separators in either Unix or Windows format.
    263     * The output will contain separators in the format of the system.
    264     * <p>
    265     * A trailing slash will be removed.
    266     * A double slash will be merged to a single slash (but UNC names are handled).
    267     * A single dot path segment will be removed.
    268     * A double dot will cause that path segment and the one before to be removed.
    269     * If the double dot has no parent path segment to work with, {@code null}
    270     * is returned.
    271     * <p>
    272     * The output will be the same on both Unix and Windows except
    273     * for the separator character.
    274     * <pre>
    275     * /foo//               --&gt;   /foo
    276     * /foo/./              --&gt;   /foo
    277     * /foo/../bar          --&gt;   /bar
    278     * /foo/../bar/         --&gt;   /bar
    279     * /foo/../bar/../baz   --&gt;   /baz
    280     * //foo//./bar         --&gt;   /foo/bar
    281     * /../                 --&gt;   null
    282     * ../foo               --&gt;   null
    283     * foo/bar/..           --&gt;   foo
    284     * foo/../../bar        --&gt;   null
    285     * foo/../bar           --&gt;   bar
    286     * //server/foo/../bar  --&gt;   //server/bar
    287     * //server/../bar      --&gt;   null
    288     * C:\foo\..\bar        --&gt;   C:\bar
    289     * C:\..\bar            --&gt;   null
    290     * ~/foo/../bar/        --&gt;   ~/bar
    291     * ~/../bar             --&gt;   null
    292     * </pre>
    293     * (Note the file separator returned will be correct for Windows/Unix)
    294     *
    295     * @param fileName  the fileName to normalize, null returns null
    296     * @return the normalized fileName, or null if invalid. Null bytes inside string will be removed
    297     */
    298    public static String normalizeNoEndSeparator(final String fileName) {
    299        return doNormalize(fileName, SYSTEM_SEPARATOR, false);
    300    }
    301
    302    /**
    303     * Normalizes a path, removing double and single dot path steps,
    304     * and removing any final directory separator.
    305     * <p>
    306     * This method normalizes a path to a standard format.
    307     * The input may contain separators in either Unix or Windows format.
    308     * The output will contain separators in the format specified.
    309     * <p>
    310     * A trailing slash will be removed.
    311     * A double slash will be merged to a single slash (but UNC names are handled).
    312     * A single dot path segment will be removed.
    313     * A double dot will cause that path segment and the one before to be removed.
    314     * If the double dot has no parent path segment to work with, {@code null}
    315     * is returned.
    316     * <p>
    317     * The output will be the same on both Unix and Windows including
    318     * the separator character.
    319     * <pre>
    320     * /foo//               --&gt;   /foo
    321     * /foo/./              --&gt;   /foo
    322     * /foo/../bar          --&gt;   /bar
    323     * /foo/../bar/         --&gt;   /bar
    324     * /foo/../bar/../baz   --&gt;   /baz
    325     * //foo//./bar         --&gt;   /foo/bar
    326     * /../                 --&gt;   null
    327     * ../foo               --&gt;   null
    328     * foo/bar/..           --&gt;   foo
    329     * foo/../../bar        --&gt;   null
    330     * foo/../bar           --&gt;   bar
    331     * //server/foo/../bar  --&gt;   //server/bar
    332     * //server/../bar      --&gt;   null
    333     * C:\foo\..\bar        --&gt;   C:\bar
    334     * C:\..\bar            --&gt;   null
    335     * ~/foo/../bar/        --&gt;   ~/bar
    336     * ~/../bar             --&gt;   null
    337     * </pre>
    338     *
    339     * @param fileName  the fileName to normalize, null returns null
    340     * @param unixSeparator {@code true} if a unix separator should
    341     * be used or {@code false} if a windows separator should be used.
    342     * @return the normalized fileName, or null if invalid. Null bytes inside string will be removed
    343     * @since 2.0
    344     */
    345    public static String normalizeNoEndSeparator(final String fileName, final boolean unixSeparator) {
    346         final char separator = unixSeparator ? UNIX_SEPARATOR : WINDOWS_SEPARATOR;
    347        return doNormalize(fileName, separator, false);
    348    }
    349
    350    /**
    351     * Internal method to perform the normalization.
    352     *
    353     * @param fileName  the fileName
    354     * @param separator The separator character to use
    355     * @param keepSeparator  true to keep the final separator
    356     * @return the normalized fileName. Null bytes inside string will be removed.
    357     */
    358    private static String doNormalize(final String fileName, final char separator, final boolean keepSeparator) {
    359        if (fileName == null) {
    360            return null;
    361        }
    362
    363        failIfNullBytePresent(fileName);
    364
    365        int size = fileName.length();
    366        if (size == 0) {
    367            return fileName;
    368        }
    369        final int prefix = getPrefixLength(fileName);
    370        if (prefix < 0) {
    371            return null;
    372        }
    373
    374        final char[] array = new char[size + 2];  // +1 for possible extra slash, +2 for arraycopy
    375        fileName.getChars(0, fileName.length(), array, 0);
    376
    377        // fix separators throughout
    378        final char otherSeparator = separator == SYSTEM_SEPARATOR ? OTHER_SEPARATOR : SYSTEM_SEPARATOR;
    379        for (int i = 0; i < array.length; i++) {
    380            if (array[i] == otherSeparator) {
    381                array[i] = separator;
    382            }
    383        }
    384
    385        // add extra separator on the end to simplify code below
    386        boolean lastIsDirectory = true;
    387        if (array[size - 1] != separator) {
    388            array[size++] = separator;
    389            lastIsDirectory = false;
    390        }
    391
    392        // adjoining slashes
    393        for (int i = prefix + 1; i < size; i++) {
    394            if (array[i] == separator && array[i - 1] == separator) {
    395                System.arraycopy(array, i, array, i - 1, size - i);
    396                size--;
    397                i--;
    398            }
    399        }
    400
    401        // dot slash
    402        for (int i = prefix + 1; i < size; i++) {
    403            if (array[i] == separator && array[i - 1] == '.' &&
    404                    (i == prefix + 1 || array[i - 2] == separator)) {
    405                if (i == size - 1) {
    406                    lastIsDirectory = true;
    407                }
    408                System.arraycopy(array, i + 1, array, i - 1, size - i);
    409                size -=2;
    410                i--;
    411            }
    412        }
    413
    414        // double dot slash
    415        outer:
    416        for (int i = prefix + 2; i < size; i++) {
    417            if (array[i] == separator && array[i - 1] == '.' && array[i - 2] == '.' &&
    418                    (i == prefix + 2 || array[i - 3] == separator)) {
    419                if (i == prefix + 2) {
    420                    return null;
    421                }
    422                if (i == size - 1) {
    423                    lastIsDirectory = true;
    424                }
    425                int j;
    426                for (j = i - 4 ; j >= prefix; j--) {
    427                    if (array[j] == separator) {
    428                        // remove b/../ from a/b/../c
    429                        System.arraycopy(array, i + 1, array, j + 1, size - i);
    430                        size -= i - j;
    431                        i = j + 1;
    432                        continue outer;
    433                    }
    434                }
    435                // remove a/../ from a/../c
    436                System.arraycopy(array, i + 1, array, prefix, size - i);
    437                size -= i + 1 - prefix;
    438                i = prefix + 1;
    439            }
    440        }
    441
    442        if (size <= 0) {  // should never be less than 0
    443            return EMPTY_STRING;
    444        }
    445        if (size <= prefix) {  // should never be less than prefix
    446            return new String(array, 0, size);
    447        }
    448        if (lastIsDirectory && keepSeparator) {
    449            return new String(array, 0, size);  // keep trailing separator
    450        }
    451        return new String(array, 0, size - 1);  // lose trailing separator
    452    }
    453
    454    //-----------------------------------------------------------------------
    455    /**
    456     * Concatenates a fileName to a base path using normal command line style rules.
    457     * <p>
    458     * The effect is equivalent to resultant directory after changing
    459     * directory to the first argument, followed by changing directory to
    460     * the second argument.
    461     * <p>
    462     * The first argument is the base path, the second is the path to concatenate.
    463     * The returned path is always normalized via {@link #normalize(String)},
    464     * thus <code>..</code> is handled.
    465     * <p>
    466     * If <code>pathToAdd</code> is absolute (has an absolute prefix), then
    467     * it will be normalized and returned.
    468     * Otherwise, the paths will be joined, normalized and returned.
    469     * <p>
    470     * The output will be the same on both Unix and Windows except
    471     * for the separator character.
    472     * <pre>
    473     * /foo/      + bar        --&gt;  /foo/bar
    474     * /foo       + bar        --&gt;  /foo/bar
    475     * /foo       + /bar       --&gt;  /bar
    476     * /foo       + C:/bar     --&gt;  C:/bar
    477     * /foo       + C:bar      --&gt;  C:bar (*)
    478     * /foo/a/    + ../bar     --&gt;  /foo/bar
    479     * /foo/      + ../../bar  --&gt;  null
    480     * /foo/      + /bar       --&gt;  /bar
    481     * /foo/..    + /bar       --&gt;  /bar
    482     * /foo       + bar/c.txt  --&gt;  /foo/bar/c.txt
    483     * /foo/c.txt + bar        --&gt;  /foo/c.txt/bar (!)
    484     * </pre>
    485     * (*) Note that the Windows relative drive prefix is unreliable when
    486     * used with this method.
    487     * (!) Note that the first parameter must be a path. If it ends with a name, then
    488     * the name will be built into the concatenated path. If this might be a problem,
    489     * use {@link #getFullPath(String)} on the base path argument.
    490     *
    491     * @param basePath  the base path to attach to, always treated as a path
    492     * @param fullFileNameToAdd  the fileName (or path) to attach to the base
    493     * @return the concatenated path, or null if invalid.  Null bytes inside string will be removed
    494     */
    495    public static String concat(final String basePath, final String fullFileNameToAdd) {
    496        final int prefix = getPrefixLength(fullFileNameToAdd);
    497        if (prefix < 0) {
    498            return null;
    499        }
    500        if (prefix > 0) {
    501            return normalize(fullFileNameToAdd);
    502        }
    503        if (basePath == null) {
    504            return null;
    505        }
    506        final int len = basePath.length();
    507        if (len == 0) {
    508            return normalize(fullFileNameToAdd);
    509        }
    510        final char ch = basePath.charAt(len - 1);
    511        if (isSeparator(ch)) {
    512            return normalize(basePath + fullFileNameToAdd);
    513        }
    514        return normalize(basePath + '/' + fullFileNameToAdd);
    515    }
    516
    517    /**
    518     * Determines whether the {@code parent} directory contains the {@code child} element (a file or directory).
    519     * <p>
    520     * The files names are expected to be normalized.
    521     * </p>
    522     *
    523     * Edge cases:
    524     * <ul>
    525     * <li>A {@code directory} must not be null: if null, throw IllegalArgumentException</li>
    526     * <li>A directory does not contain itself: return false</li>
    527     * <li>A null child file is not contained in any parent: return false</li>
    528     * </ul>
    529     *
    530     * @param canonicalParent
    531     *            the file to consider as the parent.
    532     * @param canonicalChild
    533     *            the file to consider as the child.
    534     * @return true is the candidate leaf is under by the specified composite. False otherwise.
    535     * @throws IOException
    536     *             if an IO error occurs while checking the files.
    537     * @since 2.2
    538     * @see FileUtils#directoryContains(File, File)
    539     */
    540    public static boolean directoryContains(final String canonicalParent, final String canonicalChild)
    541            throws IOException {
    542
    543        // Fail fast against NullPointerException
    544        if (canonicalParent == null) {
    545            throw new IllegalArgumentException("Directory must not be null");
    546        }
    547
    548        if (canonicalChild == null) {
    549            return false;
    550        }
    551
    552        if (IOCase.SYSTEM.checkEquals(canonicalParent, canonicalChild)) {
    553            return false;
    554        }
    555
    556        return IOCase.SYSTEM.checkStartsWith(canonicalChild, canonicalParent);
    557    }
    558
    559    //-----------------------------------------------------------------------
    560    /**
    561     * Converts all separators to the Unix separator of forward slash.
    562     *
    563     * @param path  the path to be changed, null ignored
    564     * @return the updated path
    565     */
    566    public static String separatorsToUnix(final String path) {
    567        if (path == null || path.indexOf(WINDOWS_SEPARATOR) == NOT_FOUND) {
    568            return path;
    569        }
    570        return path.replace(WINDOWS_SEPARATOR, UNIX_SEPARATOR);
    571    }
    572
    573    /**
    574     * Converts all separators to the Windows separator of backslash.
    575     *
    576     * @param path  the path to be changed, null ignored
    577     * @return the updated path
    578     */
    579    public static String separatorsToWindows(final String path) {
    580        if (path == null || path.indexOf(UNIX_SEPARATOR) == NOT_FOUND) {
    581            return path;
    582        }
    583        return path.replace(UNIX_SEPARATOR, WINDOWS_SEPARATOR);
    584    }
    585
    586    /**
    587     * Converts all separators to the system separator.
    588     *
    589     * @param path  the path to be changed, null ignored
    590     * @return the updated path
    591     */
    592    public static String separatorsToSystem(final String path) {
    593        if (path == null) {
    594            return null;
    595        }
    596        return isSystemWindows() ? separatorsToWindows(path) : separatorsToUnix(path);
    597    }
    598
    599    //-----------------------------------------------------------------------
    600    /**
    601     * Returns the length of the fileName prefix, such as <code>C:/</code> or <code>~/</code>.
    602     * <p>
    603     * This method will handle a file in either Unix or Windows format.
    604     * <p>
    605     * The prefix length includes the first slash in the full fileName
    606     * if applicable. Thus, it is possible that the length returned is greater
    607     * than the length of the input string.
    608     * <pre>
    609     * Windows:
    610     * a\b\c.txt           --&gt; ""          --&gt; relative
    611     * \a\b\c.txt          --&gt; "\"         --&gt; current drive absolute
    612     * C:a\b\c.txt         --&gt; "C:"        --&gt; drive relative
    613     * C:\a\b\c.txt        --&gt; "C:\"       --&gt; absolute
    614     * \\server\a\b\c.txt  --&gt; "\\server\" --&gt; UNC
    615     * \\\a\b\c.txt        --&gt;  error, length = -1
    616     *
    617     * Unix:
    618     * a/b/c.txt           --&gt; ""          --&gt; relative
    619     * /a/b/c.txt          --&gt; "/"         --&gt; absolute
    620     * ~/a/b/c.txt         --&gt; "~/"        --&gt; current user
    621     * ~                   --&gt; "~/"        --&gt; current user (slash added)
    622     * ~user/a/b/c.txt     --&gt; "~user/"    --&gt; named user
    623     * ~user               --&gt; "~user/"    --&gt; named user (slash added)
    624     * //server/a/b/c.txt  --&gt; "//server/"
    625     * ///a/b/c.txt        --&gt; error, length = -1
    626     * </pre>
    627     * <p>
    628     * The output will be the same irrespective of the machine that the code is running on.
    629     * ie. both Unix and Windows prefixes are matched regardless.
    630     *
    631     * Note that a leading // (or \\) is used to indicate a UNC name on Windows.
    632     * These must be followed by a server name, so double-slashes are not collapsed
    633     * to a single slash at the start of the fileName.
    634     *
    635     * @param fileName  the fileName to find the prefix in, null returns -1
    636     * @return the length of the prefix, -1 if invalid or null
    637     */
    638    public static int getPrefixLength(final String fileName) {
    639        if (fileName == null) {
    640            return NOT_FOUND;
    641        }
    642        final int len = fileName.length();
    643        if (len == 0) {
    644            return 0;
    645        }
    646        char ch0 = fileName.charAt(0);
    647        if (ch0 == ':') {
    648            return NOT_FOUND;
    649        }
    650        if (len == 1) {
    651            if (ch0 == '~') {
    652                return 2;  // return a length greater than the input
    653            }
    654            return isSeparator(ch0) ? 1 : 0;
    655        }
    656        if (ch0 == '~') {
    657            int posUnix = fileName.indexOf(UNIX_SEPARATOR, 1);
    658            int posWin = fileName.indexOf(WINDOWS_SEPARATOR, 1);
    659            if (posUnix == NOT_FOUND && posWin == NOT_FOUND) {
    660                return len + 1;  // return a length greater than the input
    661            }
    662            posUnix = posUnix == NOT_FOUND ? posWin : posUnix;
    663            posWin = posWin == NOT_FOUND ? posUnix : posWin;
    664            return Math.min(posUnix, posWin) + 1;
    665        }
    666        final char ch1 = fileName.charAt(1);
    667        if (ch1 == ':') {
    668            ch0 = Character.toUpperCase(ch0);
    669            if (ch0 >= 'A' && ch0 <= 'Z') {
    670                if (len == 2 || isSeparator(fileName.charAt(2)) == false) {
    671                    return 2;
    672                }
    673                return 3;
    674            } else if (ch0 == UNIX_SEPARATOR) {
    675                return 1;
    676            }
    677            return NOT_FOUND;
    678
    679        } else if (isSeparator(ch0) && isSeparator(ch1)) {
    680            int posUnix = fileName.indexOf(UNIX_SEPARATOR, 2);
    681            int posWin = fileName.indexOf(WINDOWS_SEPARATOR, 2);
    682            if (posUnix == NOT_FOUND && posWin == NOT_FOUND || posUnix == 2 || posWin == 2) {
    683                return NOT_FOUND;
    684            }
    685            posUnix = posUnix == NOT_FOUND ? posWin : posUnix;
    686            posWin = posWin == NOT_FOUND ? posUnix : posWin;
    687            final int pos = Math.min(posUnix, posWin) + 1;
    688            final String hostnamePart = fileName.substring(2, pos - 1);
    689            return isValidHostName(hostnamePart) ? pos : NOT_FOUND;
    690        } else {
    691            return isSeparator(ch0) ? 1 : 0;
    692        }
    693    }
    694
    695    /**
    696     * Returns the index of the last directory separator character.
    697     * <p>
    698     * This method will handle a file in either Unix or Windows format.
    699     * The position of the last forward or backslash is returned.
    700     * <p>
    701     * The output will be the same irrespective of the machine that the code is running on.
    702     *
    703     * @param fileName  the fileName to find the last path separator in, null returns -1
    704     * @return the index of the last separator character, or -1 if there
    705     * is no such character
    706     */
    707    public static int indexOfLastSeparator(final String fileName) {
    708        if (fileName == null) {
    709            return NOT_FOUND;
    710        }
    711        final int lastUnixPos = fileName.lastIndexOf(UNIX_SEPARATOR);
    712        final int lastWindowsPos = fileName.lastIndexOf(WINDOWS_SEPARATOR);
    713        return Math.max(lastUnixPos, lastWindowsPos);
    714    }
    715
    716    /**
    717     * Returns the index of the last extension separator character, which is a dot.
    718     * <p>
    719     * This method also checks that there is no directory separator after the last dot. To do this it uses
    720     * {@link #indexOfLastSeparator(String)} which will handle a file in either Unix or Windows format.
    721     * </p>
    722     * <p>
    723     * The output will be the same irrespective of the machine that the code is running on, with the
    724     * exception of a possible {@link IllegalArgumentException} on Windows (see below).
    725     * </p>
    726     * <b>Note:</b> This method used to have a hidden problem for names like "foo.exe:bar.txt".
    727     * In this case, the name wouldn't be the name of a file, but the identifier of an
    728     * alternate data stream (bar.txt) on the file foo.exe. The method used to return
    729     * ".txt" here, which would be misleading. Commons IO 2.7, and later versions, are throwing
    730     * an {@link IllegalArgumentException} for names like this.
    731     *
    732     * @param fileName
    733     *            the fileName to find the last extension separator in, null returns -1
    734     * @return the index of the last extension separator character, or -1 if there is no such character
    735     * @throws IllegalArgumentException <b>Windows only:</b> The fileName parameter is, in fact,
    736     * the identifier of an Alternate Data Stream, for example "foo.exe:bar.txt".
    737     */
    738    public static int indexOfExtension(final String fileName) throws IllegalArgumentException {
    739        if (fileName == null) {
    740            return NOT_FOUND;
    741        }
    742        if (isSystemWindows()) {
    743            // Special handling for NTFS ADS: Don't accept colon in the fileName.
    744            final int offset = fileName.indexOf(':', getAdsCriticalOffset(fileName));
    745            if (offset != -1) {
    746                throw new IllegalArgumentException("NTFS ADS separator (':') in file name is forbidden.");
    747            }
    748        }
    749        final int extensionPos = fileName.lastIndexOf(EXTENSION_SEPARATOR);
    750        final int lastSeparator = indexOfLastSeparator(fileName);
    751        return lastSeparator > extensionPos ? NOT_FOUND : extensionPos;
    752    }
    753
    754    //-----------------------------------------------------------------------
    755    /**
    756     * Gets the prefix from a full fileName, such as <code>C:/</code>
    757     * or <code>~/</code>.
    758     * <p>
    759     * This method will handle a file in either Unix or Windows format.
    760     * The prefix includes the first slash in the full fileName where applicable.
    761     * <pre>
    762     * Windows:
    763     * a\b\c.txt           --&gt; ""          --&gt; relative
    764     * \a\b\c.txt          --&gt; "\"         --&gt; current drive absolute
    765     * C:a\b\c.txt         --&gt; "C:"        --&gt; drive relative
    766     * C:\a\b\c.txt        --&gt; "C:\"       --&gt; absolute
    767     * \\server\a\b\c.txt  --&gt; "\\server\" --&gt; UNC
    768     *
    769     * Unix:
    770     * a/b/c.txt           --&gt; ""          --&gt; relative
    771     * /a/b/c.txt          --&gt; "/"         --&gt; absolute
    772     * ~/a/b/c.txt         --&gt; "~/"        --&gt; current user
    773     * ~                   --&gt; "~/"        --&gt; current user (slash added)
    774     * ~user/a/b/c.txt     --&gt; "~user/"    --&gt; named user
    775     * ~user               --&gt; "~user/"    --&gt; named user (slash added)
    776     * </pre>
    777     * <p>
    778     * The output will be the same irrespective of the machine that the code is running on.
    779     * ie. both Unix and Windows prefixes are matched regardless.
    780     *
    781     * @param fileName  the fileName to query, null returns null
    782     * @return the prefix of the file, null if invalid. Null bytes inside string will be removed
    783     */
    784    public static String getPrefix(final String fileName) {
    785        if (fileName == null) {
    786            return null;
    787        }
    788        final int len = getPrefixLength(fileName);
    789        if (len < 0) {
    790            return null;
    791        }
    792        if (len > fileName.length()) {
    793            failIfNullBytePresent(fileName + UNIX_SEPARATOR);
    794            return fileName + UNIX_SEPARATOR;
    795        }
    796        final String path = fileName.substring(0, len);
    797        failIfNullBytePresent(path);
    798        return path;
    799    }
    800
    801    /**
    802     * Gets the path from a full fileName, which excludes the prefix.
    803     * <p>
    804     * This method will handle a file in either Unix or Windows format.
    805     * The method is entirely text based, and returns the text before and
    806     * including the last forward or backslash.
    807     * <pre>
    808     * C:\a\b\c.txt --&gt; a\b\
    809     * ~/a/b/c.txt  --&gt; a/b/
    810     * a.txt        --&gt; ""
    811     * a/b/c        --&gt; a/b/
    812     * a/b/c/       --&gt; a/b/c/
    813     * </pre>
    814     * <p>
    815     * The output will be the same irrespective of the machine that the code is running on.
    816     * <p>
    817     * This method drops the prefix from the result.
    818     * See {@link #getFullPath(String)} for the method that retains the prefix.
    819     *
    820     * @param fileName  the fileName to query, null returns null
    821     * @return the path of the file, an empty string if none exists, null if invalid.
    822     * Null bytes inside string will be removed
    823     */
    824    public static String getPath(final String fileName) {
    825        return doGetPath(fileName, 1);
    826    }
    827
    828    /**
    829     * Gets the path from a full fileName, which excludes the prefix, and
    830     * also excluding the final directory separator.
    831     * <p>
    832     * This method will handle a file in either Unix or Windows format.
    833     * The method is entirely text based, and returns the text before the
    834     * last forward or backslash.
    835     * <pre>
    836     * C:\a\b\c.txt --&gt; a\b
    837     * ~/a/b/c.txt  --&gt; a/b
    838     * a.txt        --&gt; ""
    839     * a/b/c        --&gt; a/b
    840     * a/b/c/       --&gt; a/b/c
    841     * </pre>
    842     * <p>
    843     * The output will be the same irrespective of the machine that the code is running on.
    844     * <p>
    845     * This method drops the prefix from the result.
    846     * See {@link #getFullPathNoEndSeparator(String)} for the method that retains the prefix.
    847     *
    848     * @param fileName  the fileName to query, null returns null
    849     * @return the path of the file, an empty string if none exists, null if invalid.
    850     * Null bytes inside string will be removed
    851     */
    852    public static String getPathNoEndSeparator(final String fileName) {
    853        return doGetPath(fileName, 0);
    854    }
    855
    856    /**
    857     * Does the work of getting the path.
    858     *
    859     * @param fileName  the fileName
    860     * @param separatorAdd  0 to omit the end separator, 1 to return it
    861     * @return the path. Null bytes inside string will be removed
    862     */
    863    private static String doGetPath(final String fileName, final int separatorAdd) {
    864        if (fileName == null) {
    865            return null;
    866        }
    867        final int prefix = getPrefixLength(fileName);
    868        if (prefix < 0) {
    869            return null;
    870        }
    871        final int index = indexOfLastSeparator(fileName);
    872        final int endIndex = index+separatorAdd;
    873        if (prefix >= fileName.length() || index < 0 || prefix >= endIndex) {
    874            return EMPTY_STRING;
    875        }
    876        final String path = fileName.substring(prefix, endIndex);
    877        failIfNullBytePresent(path);
    878        return path;
    879    }
    880
    881    /**
    882     * Gets the full path from a full fileName, which is the prefix + path.
    883     * <p>
    884     * This method will handle a file in either Unix or Windows format.
    885     * The method is entirely text based, and returns the text before and
    886     * including the last forward or backslash.
    887     * <pre>
    888     * C:\a\b\c.txt --&gt; C:\a\b\
    889     * ~/a/b/c.txt  --&gt; ~/a/b/
    890     * a.txt        --&gt; ""
    891     * a/b/c        --&gt; a/b/
    892     * a/b/c/       --&gt; a/b/c/
    893     * C:           --&gt; C:
    894     * C:\          --&gt; C:\
    895     * ~            --&gt; ~/
    896     * ~/           --&gt; ~/
    897     * ~user        --&gt; ~user/
    898     * ~user/       --&gt; ~user/
    899     * </pre>
    900     * <p>
    901     * The output will be the same irrespective of the machine that the code is running on.
    902     *
    903     * @param fileName  the fileName to query, null returns null
    904     * @return the path of the file, an empty string if none exists, null if invalid
    905     */
    906    public static String getFullPath(final String fileName) {
    907        return doGetFullPath(fileName, true);
    908    }
    909
    910    /**
    911     * Gets the full path from a full fileName, which is the prefix + path,
    912     * and also excluding the final directory separator.
    913     * <p>
    914     * This method will handle a file in either Unix or Windows format.
    915     * The method is entirely text based, and returns the text before the
    916     * last forward or backslash.
    917     * <pre>
    918     * C:\a\b\c.txt --&gt; C:\a\b
    919     * ~/a/b/c.txt  --&gt; ~/a/b
    920     * a.txt        --&gt; ""
    921     * a/b/c        --&gt; a/b
    922     * a/b/c/       --&gt; a/b/c
    923     * C:           --&gt; C:
    924     * C:\          --&gt; C:\
    925     * ~            --&gt; ~
    926     * ~/           --&gt; ~
    927     * ~user        --&gt; ~user
    928     * ~user/       --&gt; ~user
    929     * </pre>
    930     * <p>
    931     * The output will be the same irrespective of the machine that the code is running on.
    932     *
    933     * @param fileName  the fileName to query, null returns null
    934     * @return the path of the file, an empty string if none exists, null if invalid
    935     */
    936    public static String getFullPathNoEndSeparator(final String fileName) {
    937        return doGetFullPath(fileName, false);
    938    }
    939
    940    /**
    941     * Does the work of getting the path.
    942     *
    943     * @param fileName  the fileName
    944     * @param includeSeparator  true to include the end separator
    945     * @return the path
    946     */
    947    private static String doGetFullPath(final String fileName, final boolean includeSeparator) {
    948        if (fileName == null) {
    949            return null;
    950        }
    951        final int prefix = getPrefixLength(fileName);
    952        if (prefix < 0) {
    953            return null;
    954        }
    955        if (prefix >= fileName.length()) {
    956            if (includeSeparator) {
    957                return getPrefix(fileName);  // add end slash if necessary
    958            }
    959            return fileName;
    960        }
    961        final int index = indexOfLastSeparator(fileName);
    962        if (index < 0) {
    963            return fileName.substring(0, prefix);
    964        }
    965        int end = index + (includeSeparator ?  1 : 0);
    966        if (end == 0) {
    967            end++;
    968        }
    969        return fileName.substring(0, end);
    970    }
    971
    972    /**
    973     * Gets the name minus the path from a full fileName.
    974     * <p>
    975     * This method will handle a file in either Unix or Windows format.
    976     * The text after the last forward or backslash is returned.
    977     * <pre>
    978     * a/b/c.txt --&gt; c.txt
    979     * a.txt     --&gt; a.txt
    980     * a/b/c     --&gt; c
    981     * a/b/c/    --&gt; ""
    982     * </pre>
    983     * <p>
    984     * The output will be the same irrespective of the machine that the code is running on.
    985     *
    986     * @param fileName  the fileName to query, null returns null
    987     * @return the name of the file without the path, or an empty string if none exists.
    988     * Null bytes inside string will be removed
    989     */
    990    public static String getName(final String fileName) {
    991        if (fileName == null) {
    992            return null;
    993        }
    994        failIfNullBytePresent(fileName);
    995        final int index = indexOfLastSeparator(fileName);
    996        return fileName.substring(index + 1);
    997    }
    998
    999    /**
    1000     * Check the input for null bytes, a sign of unsanitized data being passed to to file level functions.
    1001     *
    1002     * This may be used for poison byte attacks.
    1003     * @param path the path to check
    1004     */
    1005    private static void failIfNullBytePresent(final String path) {
    1006        final int len = path.length();
    1007        for (int i = 0; i < len; i++) {
    1008            if (path.charAt(i) == 0) {
    1009                throw new IllegalArgumentException("Null byte present in file/path name. There are no " +
    1010                        "known legitimate use cases for such data, but several injection attacks may use it");
    1011            }
    1012        }
    1013    }
    1014
    1015    /**
    1016     * Gets the base name, minus the full path and extension, from a full fileName.
    1017     * <p>
    1018     * This method will handle a file in either Unix or Windows format.
    1019     * The text after the last forward or backslash and before the last dot is returned.
    1020     * <pre>
    1021     * a/b/c.txt --&gt; c
    1022     * a.txt     --&gt; a
    1023     * a/b/c     --&gt; c
    1024     * a/b/c/    --&gt; ""
    1025     * </pre>
    1026     * <p>
    1027     * The output will be the same irrespective of the machine that the code is running on.
    1028     *
    1029     * @param fileName  the fileName to query, null returns null
    1030     * @return the name of the file without the path, or an empty string if none exists. Null bytes inside string
    1031     * will be removed
    1032     */
    1033    public static String getBaseName(final String fileName) {
    1034        return removeExtension(getName(fileName));
    1035    }
    1036
    1037    /**
    1038     * Gets the extension of a fileName.
    1039     * <p>
    1040     * This method returns the textual part of the fileName after the last dot.
    1041     * There must be no directory separator after the dot.
    1042     * <pre>
    1043     * foo.txt      --&gt; "txt"
    1044     * a/b/c.jpg    --&gt; "jpg"
    1045     * a/b.txt/c    --&gt; ""
    1046     * a/b/c        --&gt; ""
    1047     * </pre>
    1048     * <p>
    1049     * The output will be the same irrespective of the machine that the code is running on, with the
    1050     * exception of a possible {@link IllegalArgumentException} on Windows (see below).
    1051     * </p>
    1052     * <p>
    1053     * <b>Note:</b> This method used to have a hidden problem for names like "foo.exe:bar.txt".
    1054     * In this case, the name wouldn't be the name of a file, but the identifier of an
    1055     * alternate data stream (bar.txt) on the file foo.exe. The method used to return
    1056     * ".txt" here, which would be misleading. Commons IO 2.7, and later versions, are throwing
    1057     * an {@link IllegalArgumentException} for names like this.
    1058     *
    1059     * @param fileName the fileName to retrieve the extension of.
    1060     * @return the extension of the file or an empty string if none exists or {@code null}
    1061     * if the fileName is {@code null}.
    1062     * @throws IllegalArgumentException <b>Windows only:</b> The fileName parameter is, in fact,
    1063     * the identifier of an Alternate Data Stream, for example "foo.exe:bar.txt".
    1064     */
    1065    public static String getExtension(final String fileName) throws IllegalArgumentException {
    1066        if (fileName == null) {
    1067            return null;
    1068        }
    1069        final int index = indexOfExtension(fileName);
    1070        if (index == NOT_FOUND) {
    1071            return EMPTY_STRING;
    1072        }
    1073        return fileName.substring(index + 1);
    1074    }
    1075
    1076    /**
    1077     * Special handling for NTFS ADS: Don't accept colon in the fileName.
    1078     *
    1079     * @param fileName a file name
    1080     * @return ADS offsets.
    1081     */
    1082    private static int getAdsCriticalOffset(final String fileName) {
    1083        // Step 1: Remove leading path segments.
    1084        final int offset1 = fileName.lastIndexOf(SYSTEM_SEPARATOR);
    1085        final int offset2 = fileName.lastIndexOf(OTHER_SEPARATOR);
    1086        if (offset1 == -1) {
    1087            if (offset2 == -1) {
    1088                return 0;
    1089            }
    1090            return offset2 + 1;
    1091        }
    1092        if (offset2 == -1) {
    1093            return offset1 + 1;
    1094        }
    1095        return Math.max(offset1, offset2) + 1;
    1096    }
    1097
    1098    //-----------------------------------------------------------------------
    1099    /**
    1100     * Removes the extension from a fileName.
    1101     * <p>
    1102     * This method returns the textual part of the fileName before the last dot.
    1103     * There must be no directory separator after the dot.
    1104     * <pre>
    1105     * foo.txt    --&gt; foo
    1106     * a\b\c.jpg  --&gt; a\b\c
    1107     * a\b\c      --&gt; a\b\c
    1108     * a.b\c      --&gt; a.b\c
    1109     * </pre>
    1110     * <p>
    1111     * The output will be the same irrespective of the machine that the code is running on.
    1112     *
    1113     * @param fileName  the fileName to query, null returns null
    1114     * @return the fileName minus the extension
    1115     */
    1116    public static String removeExtension(final String fileName) {
    1117        if (fileName == null) {
    1118            return null;
    1119        }
    1120        failIfNullBytePresent(fileName);
    1121
    1122        final int index = indexOfExtension(fileName);
    1123        if (index == NOT_FOUND) {
    1124            return fileName;
    1125        }
    1126        return fileName.substring(0, index);
    1127    }
    1128
    1129    //-----------------------------------------------------------------------
    1130    /**
    1131     * Checks whether two fileNames are equal exactly.
    1132     * <p>
    1133     * No processing is performed on the fileNames other than comparison,
    1134     * thus this is merely a null-safe case-sensitive equals.
    1135     *
    1136     * @param fileName1  the first fileName to query, may be null
    1137     * @param fileName2  the second fileName to query, may be null
    1138     * @return true if the fileNames are equal, null equals null
    1139     * @see IOCase#SENSITIVE
    1140     */
    1141    public static boolean equals(final String fileName1, final String fileName2) {
    1142        return equals(fileName1, fileName2, false, IOCase.SENSITIVE);
    1143    }
    1144
    1145    /**
    1146     * Checks whether two fileNames are equal using the case rules of the system.
    1147     * <p>
    1148     * No processing is performed on the fileNames other than comparison.
    1149     * The check is case-sensitive on Unix and case-insensitive on Windows.
    1150     *
    1151     * @param fileName1  the first fileName to query, may be null
    1152     * @param fileName2  the second fileName to query, may be null
    1153     * @return true if the fileNames are equal, null equals null
    1154     * @see IOCase#SYSTEM
    1155     */
    1156    public static boolean equalsOnSystem(final String fileName1, final String fileName2) {
    1157        return equals(fileName1, fileName2, false, IOCase.SYSTEM);
    1158    }
    1159
    1160    //-----------------------------------------------------------------------
    1161    /**
    1162     * Checks whether two fileNames are equal after both have been normalized.
    1163     * <p>
    1164     * Both fileNames are first passed to {@link #normalize(String)}.
    1165     * The check is then performed in a case-sensitive manner.
    1166     *
    1167     * @param fileName1  the first fileName to query, may be null
    1168     * @param fileName2  the second fileName to query, may be null
    1169     * @return true if the fileNames are equal, null equals null
    1170     * @see IOCase#SENSITIVE
    1171     */
    1172    public static boolean equalsNormalized(final String fileName1, final String fileName2) {
    1173        return equals(fileName1, fileName2, true, IOCase.SENSITIVE);
    1174    }
    1175
    1176    /**
    1177     * Checks whether two fileNames are equal after both have been normalized
    1178     * and using the case rules of the system.
    1179     * <p>
    1180     * Both fileNames are first passed to {@link #normalize(String)}.
    1181     * The check is then performed case-sensitive on Unix and
    1182     * case-insensitive on Windows.
    1183     *
    1184     * @param fileName1  the first fileName to query, may be null
    1185     * @param fileName2  the second fileName to query, may be null
    1186     * @return true if the fileNames are equal, null equals null
    1187     * @see IOCase#SYSTEM
    1188     */
    1189    public static boolean equalsNormalizedOnSystem(final String fileName1, final String fileName2) {
    1190        return equals(fileName1, fileName2, true, IOCase.SYSTEM);
    1191    }
    1192
    1193    /**
    1194     * Checks whether two fileNames are equal, optionally normalizing and providing
    1195     * control over the case-sensitivity.
    1196     *
    1197     * @param fileName1  the first fileName to query, may be null
    1198     * @param fileName2  the second fileName to query, may be null
    1199     * @param normalized  whether to normalize the fileNames
    1200     * @param caseSensitivity  what case sensitivity rule to use, null means case-sensitive
    1201     * @return true if the fileNames are equal, null equals null
    1202     * @since 1.3
    1203     */
    1204    public static boolean equals(
    1205            String fileName1, String fileName2,
    1206            final boolean normalized, IOCase caseSensitivity) {
    1207
    1208        if (fileName1 == null || fileName2 == null) {
    1209            return fileName1 == null && fileName2 == null;
    1210        }
    1211        if (normalized) {
    1212            fileName1 = normalize(fileName1);
    1213            fileName2 = normalize(fileName2);
    1214            Objects.requireNonNull(fileName1, "Error normalizing one or both of the file names");
    1215            Objects.requireNonNull(fileName2, "Error normalizing one or both of the file names");
    1216        }
    1217        if (caseSensitivity == null) {
    1218            caseSensitivity = IOCase.SENSITIVE;
    1219        }
    1220        return caseSensitivity.checkEquals(fileName1, fileName2);
    1221    }
    1222
    1223    //-----------------------------------------------------------------------
    1224    /**
    1225     * Checks whether the extension of the fileName is that specified.
    1226     * <p>
    1227     * This method obtains the extension as the textual part of the fileName
    1228     * after the last dot. There must be no directory separator after the dot.
    1229     * The extension check is case-sensitive on all platforms.
    1230     *
    1231     * @param fileName  the fileName to query, null returns false
    1232     * @param extension  the extension to check for, null or empty checks for no extension
    1233     * @return true if the fileName has the specified extension
    1234     * @throws java.lang.IllegalArgumentException if the supplied fileName contains null bytes
    1235     */
    1236    public static boolean isExtension(final String fileName, final String extension) {
    1237        if (fileName == null) {
    1238            return false;
    1239        }
    1240        failIfNullBytePresent(fileName);
    1241
    1242        if (extension == null || extension.isEmpty()) {
    1243            return indexOfExtension(fileName) == NOT_FOUND;
    1244        }
    1245        final String fileExt = getExtension(fileName);
    1246        return fileExt.equals(extension);
    1247    }
    1248
    1249    /**
    1250     * Checks whether the extension of the fileName is one of those specified.
    1251     * <p>
    1252     * This method obtains the extension as the textual part of the fileName
    1253     * after the last dot. There must be no directory separator after the dot.
    1254     * The extension check is case-sensitive on all platforms.
    1255     *
    1256     * @param fileName  the fileName to query, null returns false
    1257     * @param extensions  the extensions to check for, null checks for no extension
    1258     * @return true if the fileName is one of the extensions
    1259     * @throws java.lang.IllegalArgumentException if the supplied fileName contains null bytes
    1260     */
    1261    public static boolean isExtension(final String fileName, final String... extensions) {
    1262        if (fileName == null) {
    1263            return false;
    1264        }
    1265        failIfNullBytePresent(fileName);
    1266
    1267        if (extensions == null || extensions.length == 0) {
    1268            return indexOfExtension(fileName) == NOT_FOUND;
    1269        }
    1270        final String fileExt = getExtension(fileName);
    1271        for (final String extension : extensions) {
    1272            if (fileExt.equals(extension)) {
    1273                return true;
    1274            }
    1275        }
    1276        return false;
    1277    }
    1278
    1279    /**
    1280     * Checks whether the extension of the fileName is one of those specified.
    1281     * <p>
    1282     * This method obtains the extension as the textual part of the fileName
    1283     * after the last dot. There must be no directory separator after the dot.
    1284     * The extension check is case-sensitive on all platforms.
    1285     *
    1286     * @param fileName  the fileName to query, null returns false
    1287     * @param extensions  the extensions to check for, null checks for no extension
    1288     * @return true if the fileName is one of the extensions
    1289     * @throws java.lang.IllegalArgumentException if the supplied fileName contains null bytes
    1290     */
    1291    public static boolean isExtension(final String fileName, final Collection<String> extensions) {
    1292        if (fileName == null) {
    1293            return false;
    1294        }
    1295        failIfNullBytePresent(fileName);
    1296
    1297        if (extensions == null || extensions.isEmpty()) {
    1298            return indexOfExtension(fileName) == NOT_FOUND;
    1299        }
    1300        final String fileExt = getExtension(fileName);
    1301        for (final String extension : extensions) {
    1302            if (fileExt.equals(extension)) {
    1303                return true;
    1304            }
    1305        }
    1306        return false;
    1307    }
    1308
    1309    //-----------------------------------------------------------------------
    1310    /**
    1311     * Checks a fileName to see if it matches the specified wildcard matcher,
    1312     * always testing case-sensitive.
    1313     * <p>
    1314     * The wildcard matcher uses the characters '?' and '*' to represent a
    1315     * single or multiple (zero or more) wildcard characters.
    1316     * This is the same as often found on Dos/Unix command lines.
    1317     * The check is case-sensitive always.
    1318     * <pre>
    1319     * wildcardMatch("c.txt", "*.txt")      --&gt; true
    1320     * wildcardMatch("c.txt", "*.jpg")      --&gt; false
    1321     * wildcardMatch("a/b/c.txt", "a/b/*")  --&gt; true
    1322     * wildcardMatch("c.txt", "*.???")      --&gt; true
    1323     * wildcardMatch("c.txt", "*.????")     --&gt; false
    1324     * </pre>
    1325     * N.B. the sequence "*?" does not work properly at present in match strings.
    1326     *
    1327     * @param fileName  the fileName to match on
    1328     * @param wildcardMatcher  the wildcard string to match against
    1329     * @return true if the fileName matches the wildcard string
    1330     * @see IOCase#SENSITIVE
    1331     */
    1332    public static boolean wildcardMatch(final String fileName, final String wildcardMatcher) {
    1333        return wildcardMatch(fileName, wildcardMatcher, IOCase.SENSITIVE);
    1334    }
    1335
    1336    /**
    1337     * Checks a fileName to see if it matches the specified wildcard matcher
    1338     * using the case rules of the system.
    1339     * <p>
    1340     * The wildcard matcher uses the characters '?' and '*' to represent a
    1341     * single or multiple (zero or more) wildcard characters.
    1342     * This is the same as often found on Dos/Unix command lines.
    1343     * The check is case-sensitive on Unix and case-insensitive on Windows.
    1344     * <pre>
    1345     * wildcardMatch("c.txt", "*.txt")      --&gt; true
    1346     * wildcardMatch("c.txt", "*.jpg")      --&gt; false
    1347     * wildcardMatch("a/b/c.txt", "a/b/*")  --&gt; true
    1348     * wildcardMatch("c.txt", "*.???")      --&gt; true
    1349     * wildcardMatch("c.txt", "*.????")     --&gt; false
    1350     * </pre>
    1351     * N.B. the sequence "*?" does not work properly at present in match strings.
    1352     *
    1353     * @param fileName  the fileName to match on
    1354     * @param wildcardMatcher  the wildcard string to match against
    1355     * @return true if the fileName matches the wildcard string
    1356     * @see IOCase#SYSTEM
    1357     */
    1358    public static boolean wildcardMatchOnSystem(final String fileName, final String wildcardMatcher) {
    1359        return wildcardMatch(fileName, wildcardMatcher, IOCase.SYSTEM);
    1360    }
    1361
    1362    /**
    1363     * Checks a fileName to see if it matches the specified wildcard matcher
    1364     * allowing control over case-sensitivity.
    1365     * <p>
    1366     * The wildcard matcher uses the characters '?' and '*' to represent a
    1367     * single or multiple (zero or more) wildcard characters.
    1368     * N.B. the sequence "*?" does not work properly at present in match strings.
    1369     *
    1370     * @param fileName  the fileName to match on
    1371     * @param wildcardMatcher  the wildcard string to match against
    1372     * @param caseSensitivity  what case sensitivity rule to use, null means case-sensitive
    1373     * @return true if the fileName matches the wildcard string
    1374     * @since 1.3
    1375     */
    1376    public static boolean wildcardMatch(final String fileName, final String wildcardMatcher, IOCase caseSensitivity) {
    1377        if (fileName == null && wildcardMatcher == null) {
    1378            return true;
    1379        }
    1380        if (fileName == null || wildcardMatcher == null) {
    1381            return false;
    1382        }
    1383        if (caseSensitivity == null) {
    1384            caseSensitivity = IOCase.SENSITIVE;
    1385        }
    1386        final String[] wcs = splitOnTokens(wildcardMatcher);
    1387        boolean anyChars = false;
    1388        int textIdx = 0;
    1389        int wcsIdx = 0;
    1390        final Deque<int[]> backtrack = new ArrayDeque<>(wcs.length);
    1391
    1392        // loop around a backtrack stack, to handle complex * matching
    1393        do {
    1394            if (!backtrack.isEmpty()) {
    1395                final int[] array = backtrack.pop();
    1396                wcsIdx = array[0];
    1397                textIdx = array[1];
    1398                anyChars = true;
    1399            }
    1400
    1401            // loop whilst tokens and text left to process
    1402            while (wcsIdx < wcs.length) {
    1403
    1404                if (wcs[wcsIdx].equals("?")) {
    1405                    // ? so move to next text char
    1406                    textIdx++;
    1407                    if (textIdx > fileName.length()) {
    1408                        break;
    1409                    }
    1410                    anyChars = false;
    1411
    1412                } else if (wcs[wcsIdx].equals("*")) {
    1413                    // set any chars status
    1414                    anyChars = true;
    1415                    if (wcsIdx == wcs.length - 1) {
    1416                        textIdx = fileName.length();
    1417                    }
    1418
    1419                } else {
    1420                    // matching text token
    1421                    if (anyChars) {
    1422                        // any chars then try to locate text token
    1423                        textIdx = caseSensitivity.checkIndexOf(fileName, textIdx, wcs[wcsIdx]);
    1424                        if (textIdx == NOT_FOUND) {
    1425                            // token not found
    1426                            break;
    1427                        }
    1428                        final int repeat = caseSensitivity.checkIndexOf(fileName, textIdx + 1, wcs[wcsIdx]);
    1429                        if (repeat >= 0) {
    1430                            backtrack.push(new int[] {wcsIdx, repeat});
    1431                        }
    1432                    } else {
    1433                        // matching from current position
    1434                        if (!caseSensitivity.checkRegionMatches(fileName, textIdx, wcs[wcsIdx])) {
    1435                            // couldnt match token
    1436                            break;
    1437                        }
    1438                    }
    1439
    1440                    // matched text token, move text index to end of matched token
    1441                    textIdx += wcs[wcsIdx].length();
    1442                    anyChars = false;
    1443                }
    1444
    1445                wcsIdx++;
    1446            }
    1447
    1448            // full match
    1449            if (wcsIdx == wcs.length && textIdx == fileName.length()) {
    1450                return true;
    1451            }
    1452
    1453        } while (!backtrack.isEmpty());
    1454
    1455        return false;
    1456    }
    1457
    1458    /**
    1459     * Splits a string into a number of tokens.
    1460     * The text is split by '?' and '*'.
    1461     * Where multiple '*' occur consecutively they are collapsed into a single '*'.
    1462     *
    1463     * @param text  the text to split
    1464     * @return the array of tokens, never null
    1465     */
    1466    static String[] splitOnTokens(final String text) {
    1467        // used by wildcardMatch
    1468        // package level so a unit test may run on this
    1469
    1470        if (text.indexOf('?') == NOT_FOUND && text.indexOf('*') == NOT_FOUND) {
    1471            return new String[] { text };
    1472        }
    1473
    1474        final char[] array = text.toCharArray();
    1475        final ArrayList<String> list = new ArrayList<>();
    1476        final StringBuilder buffer = new StringBuilder();
    1477        char prevChar = 0;
    1478        for (final char ch : array) {
    1479            if (ch == '?' || ch == '*') {
    1480                if (buffer.length() != 0) {
    1481                    list.add(buffer.toString());
    1482                    buffer.setLength(0);
    1483                }
    1484                if (ch == '?') {
    1485                    list.add("?");
    1486                } else if (prevChar != '*') {// ch == '*' here; check if previous char was '*'
    1487                    list.add("*");
    1488                }
    1489            } else {
    1490                buffer.append(ch);
    1491            }
    1492            prevChar = ch;
    1493        }
    1494        if (buffer.length() != 0) {
    1495            list.add(buffer.toString());
    1496        }
    1497
    1498        return list.toArray(EMPTY_STRING_ARRAY);
    1499    }
    1500
    1501    /**
    1502     * Checks whether a given string is a valid host name according to
    1503     * RFC 3986.
    1504     *
    1505     * <p>Accepted are IP addresses (v4 and v6) as well as what the
    1506     * RFC calls a "reg-name". Percent encoded names don't seem to be
    1507     * valid names in UNC paths.</p>
    1508     *
    1509     * @see "https://tools.ietf.org/html/rfc3986#section-3.2.2"
    1510     * @param name the hostname to validate
    1511     * @return true if the given name is a valid host name
    1512     */
    1513    private static boolean isValidHostName(final String name) {
    1514        return isIPv6Address(name) || isRFC3986HostName(name);
    1515    }
    1516
    1517    private static final Pattern IPV4_PATTERN =
    1518        Pattern.compile("^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$");
    1519    private static final int IPV4_MAX_OCTET_VALUE = 255;
    1520
    1521    /**
    1522     * Checks whether a given string represents a valid IPv4 address.
    1523     *
    1524     * @param name the name to validate
    1525     * @return true if the given name is a valid IPv4 address
    1526     */
    1527    // mostly copied from org.apache.commons.validator.routines.InetAddressValidator#isValidInet4Address
    1528    private static boolean isIPv4Address(final String name) {
    1529        final Matcher m = IPV4_PATTERN.matcher(name);
    1530        if (!m.matches() || m.groupCount() != 4) {
    1531            return false;
    1532        }
    1533
    1534        // verify that address subgroups are legal
    1535        for (int i = 1; i <= 4; i++) {
    1536            final String ipSegment = m.group(i);
    1537            final int iIpSegment = Integer.parseInt(ipSegment);
    1538            if (iIpSegment > IPV4_MAX_OCTET_VALUE) {
    1539                return false;
    1540            }
    1541
    1542            if (ipSegment.length() > 1 && ipSegment.startsWith("0")) {
    1543                return false;
    1544            }
    1545
    1546        }
    1547
    1548        return true;
    1549    }
    1550
    1551    private static final int IPV6_MAX_HEX_GROUPS = 8;
    1552    private static final int IPV6_MAX_HEX_DIGITS_PER_GROUP = 4;
    1553    private static final int MAX_UNSIGNED_SHORT = 0xffff;
    1554    private static final int BASE_16 = 16;
    1555
    1556    // copied from org.apache.commons.validator.routines.InetAddressValidator#isValidInet6Address
    1557    /**
    1558     * Checks whether a given string represents a valid IPv6 address.
    1559     *
    1560     * @param inet6Address the name to validate
    1561     * @return true if the given name is a valid IPv6 address
    1562     */
    1563    private static boolean isIPv6Address(final String inet6Address) {
    1564        final boolean containsCompressedZeroes = inet6Address.contains("::");
    1565        if (containsCompressedZeroes && (inet6Address.indexOf("::") != inet6Address.lastIndexOf("::"))) {
    1566            return false;
    1567        }
    1568        if ((inet6Address.startsWith(":") && !inet6Address.startsWith("::"))
    1569                || (inet6Address.endsWith(":") && !inet6Address.endsWith("::"))) {
    1570            return false;
    1571        }
    1572        String[] octets = inet6Address.split(":");
    1573        if (containsCompressedZeroes) {
    1574            final List<String> octetList = new ArrayList<>(Arrays.asList(octets));
    1575            if (inet6Address.endsWith("::")) {
    1576                // String.split() drops ending empty segments
    1577                octetList.add("");
    1578            } else if (inet6Address.startsWith("::") && !octetList.isEmpty()) {
    1579                octetList.remove(0);
    1580            }
    1581            octets = octetList.toArray(EMPTY_STRING_ARRAY);
    1582        }
    1583        if (octets.length > IPV6_MAX_HEX_GROUPS) {
    1584            return false;
    1585        }
    1586        int validOctets = 0;
    1587        int emptyOctets = 0; // consecutive empty chunks
    1588        for (int index = 0; index < octets.length; index++) {
    1589            final String octet = octets[index];
    1590            if (octet.length() == 0) {
    1591                emptyOctets++;
    1592                if (emptyOctets > 1) {
    1593                    return false;
    1594                }
    1595            } else {
    1596                emptyOctets = 0;
    1597                // Is last chunk an IPv4 address?
    1598                if (index == octets.length - 1 && octet.contains(".")) {
    1599                    if (!isIPv4Address(octet)) {
    1600                        return false;
    1601                    }
    1602                    validOctets += 2;
    1603                    continue;
    1604                }
    1605                if (octet.length() > IPV6_MAX_HEX_DIGITS_PER_GROUP) {
    1606                    return false;
    1607                }
    1608                int octetInt = 0;
    1609                try {
    1610                    octetInt = Integer.parseInt(octet, BASE_16);
    1611                } catch (final NumberFormatException e) {
    1612                    return false;
    1613                }
    1614                if (octetInt < 0 || octetInt > MAX_UNSIGNED_SHORT) {
    1615                    return false;
    1616                }
    1617            }
    1618            validOctets++;
    1619        }
    1620        return validOctets <= IPV6_MAX_HEX_GROUPS && (validOctets >= IPV6_MAX_HEX_GROUPS || containsCompressedZeroes);
    1621    }
    1622
    1623    private static final Pattern REG_NAME_PART_PATTERN = Pattern.compile("^[a-zA-Z0-9][a-zA-Z0-9-]*$");
    1624
    1625    /**
    1626     * Checks whether a given string is a valid host name according to
    1627     * RFC 3986 - not accepting IP addresses.
    1628     *
    1629     * @see "https://tools.ietf.org/html/rfc3986#section-3.2.2"
    1630     * @param name the hostname to validate
    1631     * @return true if the given name is a valid host name
    1632     */
    1633    private static boolean isRFC3986HostName(final String name) {
    1634        final String[] parts = name.split("\\.", -1);
    1635        for (int i = 0; i < parts.length; i++) {
    1636            if (parts[i].length() == 0) {
    1637                // trailing dot is legal, otherwise we've hit a .. sequence
    1638                return i == parts.length - 1;
    1639            }
    1640            if (!REG_NAME_PART_PATTERN.matcher(parts[i]).matches()) {
    1641                return false;
    1642            }
    1643        }
    1644        return true;
    1645    }
    1646}