Skip to content

Conversation

@MiriamCohenDev
Copy link

Fixes a bug in FilenameUtils#normalizeNoEndSeparator when handling UNC paths that do not end with a slash.

What:

  • The function normalizeNoEndSeparator incorrectly returned null for valid UNC paths like //server because it treated them as invalid.
  • This was caused by FilenameUtils#getPrefixLength returning -1 for UNC paths without a trailing slash.

How:

  • Updated getPrefixLength to correctly handle UNC paths without a trailing slash, returning the proper prefix length instead of -1.
  • Updated and added unit tests to reflect the correct behavior, since many existing tests assumed null would be returned for these paths.

Why:

  • Previous behavior caused valid UNC paths to be treated as invalid, which could break downstream logic that relies on normalizeNoEndSeparator.
  • This change ensures proper handling of UNC paths and maintains consistency across FilenameUtils methods.

Testing:

  • Updated tests in FilenameUtilsTest to cover UNC paths without trailing slashes.
  • Verified that all tests pass with the new logic.

Files changed:

  • src/main/java/org/apache/commons/io/FilenameUtils.java
  • src/test/java/org/apache/commons/io/FilenameUtilsTest.java

Copy link
Member

@garydgregory garydgregory left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello @MiriamCohenDev

Thank you for your PR. Please see my comments. Run mvn by itself before you push and fix any issues that come up.

*/
package org.apache.commons.io;

import java.io.Console;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this import.

if (!isSeparator(ch0) || !isSeparator(ch1)) {
return isSeparator(ch0) ? 1 : 0;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove extra blank line.

}
try (BufferedOutputStream output3 =
new BufferedOutputStream(Files.newOutputStream(testFile1))) {
new BufferedOutputStream(Files.newOutputStream(testFile1))) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't make spurious edits, it makes the PR larger and takes longer to review.

assertEquals("D:\\a\\b\\c", FilenameUtils.separatorsToWindows("D:/a/b/c"));
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Files end in an empty line.

assertEquals(SEP + SEP + "server.example.org" + SEP + "a" + SEP + "b" + SEP + "c.txt", FilenameUtils.normalize("\\\\server.example.org\\a\\b\\c.txt"));
assertEquals(SEP + SEP + "server.sub.example.org" + SEP + "a" + SEP + "b" + SEP + "c.txt",
FilenameUtils.normalize("\\\\server.sub.example.org\\a\\b\\c.txt"));
FilenameUtils.normalize("\\\\server.sub.example.org\\a\\b\\c.txt"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't make spurious edits, it makes the PR larger and takes longer to review.

@garydgregory
Copy link
Member

Jira ticket is https://issues.apache.org/jira/browse/IO-475

@MiriamCohenDev MiriamCohenDev force-pushed the IO-475-fix-unc-prefix-check branch from 51f04e9 to 090b9f9 Compare January 26, 2026 14:20
@MiriamCohenDev MiriamCohenDev force-pushed the IO-475-fix-unc-prefix-check branch from 090b9f9 to 64038e1 Compare January 26, 2026 14:32
Copy link
Member

@garydgregory garydgregory left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello @MiriamCohenDev

Thank you for your updates. You missed a few spots. It's simpler to review a PR if you don't delete assertions. Instead, change them to reflect the new behavior.

void testEqualsNormalizedError_IO_128() {
assertFalse(FilenameUtils.equalsNormalizedOnSystem("//file.txt", "file.txt"));
assertFalse(FilenameUtils.equalsNormalizedOnSystem("file.txt", "//file.txt"));
assertFalse(FilenameUtils.equalsNormalizedOnSystem("//file.txt", "//file.txt"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't delete assertions, instead change it to show the new behavior here.

assertNull(FilenameUtils.getFullPathNoEndSeparator("1:"));
assertNull(FilenameUtils.getFullPathNoEndSeparator("1:a"));
assertNull(FilenameUtils.getFullPathNoEndSeparator("///a/b/c.txt"));
assertNull(FilenameUtils.getFullPathNoEndSeparator("//a"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't delete assertions, instead change it to show the new behavior here.

assertNull(FilenameUtils.getPathNoEndSeparator("1:"));
assertNull(FilenameUtils.getPathNoEndSeparator("1:a"));
assertNull(FilenameUtils.getPathNoEndSeparator("///a/b/c.txt"));
assertNull(FilenameUtils.getPathNoEndSeparator("//a"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't delete assertions, instead change it to show the new behavior here.

assertNull(FilenameUtils.getPrefix("1:"));
assertNull(FilenameUtils.getPrefix("1:a"));
assertNull(FilenameUtils.getPrefix("\\\\\\a\\b\\c.txt"));
assertNull(FilenameUtils.getPrefix("\\\\a"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't delete assertions, instead change it to show the new behavior here.

assertEquals(-1, FilenameUtils.getPrefixLength("1:"));
assertEquals(-1, FilenameUtils.getPrefixLength("1:a"));
assertEquals(-1, FilenameUtils.getPrefixLength("\\\\\\a\\b\\c.txt"));
assertEquals(-1, FilenameUtils.getPrefixLength("\\\\a"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't delete assertions, instead change it to show the new behavior here.

assertNull(FilenameUtils.normalize("1:"));
assertNull(FilenameUtils.normalize("1:a"));
assertNull(FilenameUtils.normalize("\\\\\\a\\b\\c.txt"));
assertNull(FilenameUtils.normalize("\\\\a"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't delete assertions, instead change it to show the new behavior here.

assertNull(FilenameUtils.normalizeNoEndSeparator("1:"));
assertNull(FilenameUtils.normalizeNoEndSeparator("1:a"));
assertNull(FilenameUtils.normalizeNoEndSeparator("\\\\\\a\\b\\c.txt"));
assertNull(FilenameUtils.normalizeNoEndSeparator("\\\\a"));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't delete assertions, instead change it to show the new behavior here.

Copy link
Member

@garydgregory garydgregory left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my previous comments, and run mvn without anything else and fix:

[INFO] --- checkstyle:3.6.0:check (default-cli) @ commons-io ---
[INFO] There are 16 errors reported by Checkstyle 13.0.0 with /Users/garygregory/git/commons/commons-io/src/conf/checkstyle.xml ruleset.
[ERROR] src/main/java/org/apache/commons/io/FilenameUtils.java:[940,13] (coding) FinalLocalVariable: Variable 'pos' should be declared final.
[ERROR] src/main/java/org/apache/commons/io/FilenameUtils.java:[941,16] (coding) FinalLocalVariable: Variable 'hostnamePart' should be declared final.
[ERROR] src/main/java/org/apache/commons/io/FilenameUtils.java:[942,9] (whitespace) WhitespaceAfter: 'if' is not followed by whitespace.
[ERROR] src/main/java/org/apache/commons/io/FilenameUtils.java:[942,9] (whitespace) WhitespaceAround: 'if' is not followed by whitespace.
[ERROR] src/main/java/org/apache/commons/io/FilenameUtils.java:[942,56] (whitespace) WhitespaceAround: '{' is not preceded with whitespace.
[ERROR] src/main/java/org/apache/commons/io/FilenameUtils.java:[945,9] (blocks) RightCurly: '}' at column 9 should be on the same line as the next part of a multi-block statement (one that directly contains multiple blocks: if/else-if/else, do/while or try/catch/finally).
[ERROR] src/main/java/org/apache/commons/io/FilenameUtils.java:[946,9] (whitespace) WhitespaceAfter: 'else' is not followed by whitespace.
[ERROR] src/main/java/org/apache/commons/io/FilenameUtils.java:[946,9] (whitespace) WhitespaceAround: 'else' is not followed by whitespace.
[ERROR] src/main/java/org/apache/commons/io/FilenameUtils.java:[946,13] (whitespace) WhitespaceAround: '{' is not preceded with whitespace.
[ERROR] src/test/java/org/apache/commons/io/FilenameUtilsTest.java:[410,24] (whitespace) WhitespaceAfter: ',' is not followed by whitespace.
[ERROR] src/test/java/org/apache/commons/io/FilenameUtilsTest.java:[457,24] (whitespace) WhitespaceAfter: ',' is not followed by whitespace.
[ERROR] src/test/java/org/apache/commons/io/FilenameUtilsTest.java:[561,54] (whitespace) ParenPad: '(' is followed by whitespace.
[ERROR] src/test/java/org/apache/commons/io/FilenameUtilsTest.java:[780,21] (whitespace) ParenPad: '(' is followed by whitespace.
[ERROR] src/test/java/org/apache/commons/io/FilenameUtilsTest.java:[919,21] (whitespace) ParenPad: '(' is followed by whitespace.
[ERROR] src/test/java/org/apache/commons/io/FilenameUtilsTest.java:[992,21] (whitespace) ParenPad: '(' is followed by whitespace.
[ERROR] src/test/java/org/apache/commons/io/FilenameUtilsTest.java:[1132,21] (whitespace) ParenPad: '(' is followed by whitespace.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants