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