Skip to content

Commit 2736b6f

Browse files
committed
IO-559 verify hostname part of suspected UNC paths in FileNameUtils
1 parent 7791a85 commit 2736b6f

2 files changed

Lines changed: 164 additions & 1 deletion

File tree

src/main/java/org/apache/commons/io/FilenameUtils.java

Lines changed: 146 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,12 @@
1919
import java.io.File;
2020
import java.io.IOException;
2121
import java.util.ArrayList;
22+
import java.util.Arrays;
2223
import java.util.Collection;
24+
import java.util.List;
2325
import java.util.Stack;
26+
import java.util.regex.Matcher;
27+
import java.util.regex.Pattern;
2428

2529
/**
2630
* General file name and file path manipulation utilities.
@@ -676,7 +680,9 @@ public static int getPrefixLength(final String fileName) {
676680
}
677681
posUnix = posUnix == NOT_FOUND ? posWin : posUnix;
678682
posWin = posWin == NOT_FOUND ? posUnix : posWin;
679-
return Math.min(posUnix, posWin) + 1;
683+
int pos = Math.min(posUnix, posWin) + 1;
684+
String hostnamePart = fileName.substring(2, pos - 1);
685+
return isValidHostName(hostnamePart) ? pos : NOT_FOUND;
680686
} else {
681687
return isSeparator(ch0) ? 1 : 0;
682688
}
@@ -1490,4 +1496,143 @@ static String[] splitOnTokens(final String text) {
14901496
return list.toArray( new String[ list.size() ] );
14911497
}
14921498

1499+
/**
1500+
* Checks whether a given string is a valid host name according to
1501+
* RFC 3986.
1502+
*
1503+
* <p>Accepted are IP addresses (v4 and v6) as well as what the
1504+
* RFC calls a "reg-name". Percent encoded names don't seem to be
1505+
* valid names in UNC paths.</p>
1506+
*
1507+
* @see "https://tools.ietf.org/html/rfc3986#section-3.2.2"
1508+
* @param name the hostname to validate
1509+
* @return true if the given name is a valid host name
1510+
*/
1511+
private static boolean isValidHostName(String name) {
1512+
return isIPv4Address(name) || isIPv6Address(name) || isRFC3986HostName(name);
1513+
}
1514+
1515+
private static final Pattern IPV4_PATTERN =
1516+
Pattern.compile("^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$");
1517+
private static final int IPV4_MAX_OCTET_VALUE = 255;
1518+
1519+
// mostly copied from org.apache.commons.validator.routines.InetAddressValidator#isValidInet4Address
1520+
private static boolean isIPv4Address(String name) {
1521+
Matcher m = IPV4_PATTERN.matcher(name);
1522+
if (!m.matches() || m.groupCount() != 4) {
1523+
return false;
1524+
}
1525+
1526+
// verify that address subgroups are legal
1527+
for (int i = 1; i < 5; i++) {
1528+
String ipSegment = m.group(i);
1529+
if (ipSegment == null || ipSegment.length() == 0) {
1530+
return false;
1531+
}
1532+
1533+
int iIpSegment = 0;
1534+
1535+
try {
1536+
iIpSegment = Integer.parseInt(ipSegment);
1537+
} catch(NumberFormatException e) {
1538+
return false;
1539+
}
1540+
1541+
if (iIpSegment > IPV4_MAX_OCTET_VALUE) {
1542+
return false;
1543+
}
1544+
1545+
if (ipSegment.length() > 1 && ipSegment.startsWith("0")) {
1546+
return false;
1547+
}
1548+
1549+
}
1550+
1551+
return true;
1552+
}
1553+
1554+
private static final int IPV6_MAX_HEX_GROUPS = 8;
1555+
private static final int IPV6_MAX_HEX_DIGITS_PER_GROUP = 4;
1556+
private static final int MAX_UNSIGNED_SHORT = 0xffff;
1557+
private static final int BASE_16 = 16;
1558+
1559+
// copied from org.apache.commons.validator.routines.InetAddressValidator#isValidInet6Address
1560+
private static boolean isIPv6Address(String inet6Address) {
1561+
boolean containsCompressedZeroes = inet6Address.contains("::");
1562+
if (containsCompressedZeroes && (inet6Address.indexOf("::") != inet6Address.lastIndexOf("::"))) {
1563+
return false;
1564+
}
1565+
if ((inet6Address.startsWith(":") && !inet6Address.startsWith("::"))
1566+
|| (inet6Address.endsWith(":") && !inet6Address.endsWith("::"))) {
1567+
return false;
1568+
}
1569+
String[] octets = inet6Address.split(":");
1570+
if (containsCompressedZeroes) {
1571+
List<String> octetList = new ArrayList<String>(Arrays.asList(octets));
1572+
if (inet6Address.endsWith("::")) {
1573+
// String.split() drops ending empty segments
1574+
octetList.add("");
1575+
} else if (inet6Address.startsWith("::") && !octetList.isEmpty()) {
1576+
octetList.remove(0);
1577+
}
1578+
octets = octetList.toArray(new String[octetList.size()]);
1579+
}
1580+
if (octets.length > IPV6_MAX_HEX_GROUPS) {
1581+
return false;
1582+
}
1583+
int validOctets = 0;
1584+
int emptyOctets = 0; // consecutive empty chunks
1585+
for (int index = 0; index < octets.length; index++) {
1586+
String octet = octets[index];
1587+
if (octet.length() == 0) {
1588+
emptyOctets++;
1589+
if (emptyOctets > 1) {
1590+
return false;
1591+
}
1592+
} else {
1593+
emptyOctets = 0;
1594+
// Is last chunk an IPv4 address?
1595+
if (index == octets.length - 1 && octet.contains(".")) {
1596+
if (!isIPv4Address(octet)) {
1597+
return false;
1598+
}
1599+
validOctets += 2;
1600+
continue;
1601+
}
1602+
if (octet.length() > IPV6_MAX_HEX_DIGITS_PER_GROUP) {
1603+
return false;
1604+
}
1605+
int octetInt = 0;
1606+
try {
1607+
octetInt = Integer.parseInt(octet, BASE_16);
1608+
} catch (NumberFormatException e) {
1609+
return false;
1610+
}
1611+
if (octetInt < 0 || octetInt > MAX_UNSIGNED_SHORT) {
1612+
return false;
1613+
}
1614+
}
1615+
validOctets++;
1616+
}
1617+
if (validOctets > IPV6_MAX_HEX_GROUPS || (validOctets < IPV6_MAX_HEX_GROUPS && !containsCompressedZeroes)) {
1618+
return false;
1619+
}
1620+
return true;
1621+
}
1622+
1623+
private static final Pattern REG_NAME_PART_PATTERN = Pattern.compile("^[a-zA-Z0-9][a-zA-Z0-9-]*$");
1624+
1625+
private static boolean isRFC3986HostName(String name) {
1626+
String[] parts = name.split("\\.", -1);
1627+
for (int i = 0; i < parts.length; i++) {
1628+
if (parts[i].length() == 0) {
1629+
// trailing dot is legal, otherwise we've hit a .. sequence
1630+
return i == parts.length - 1;
1631+
}
1632+
if (!REG_NAME_PART_PATTERN.matcher(parts[i]).matches()) {
1633+
return false;
1634+
}
1635+
}
1636+
return true;
1637+
}
14931638
}

src/test/java/org/apache/commons/io/FilenameUtilsTestCase.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,15 @@ public void testNormalize() throws Exception {
246246
assertEquals(null, FilenameUtils.normalize("//server/../a"));
247247
assertEquals(null, FilenameUtils.normalize("//server/.."));
248248
assertEquals(SEP + SEP + "server" + SEP + "", FilenameUtils.normalize("//server/"));
249+
250+
assertEquals(SEP + SEP + "127.0.0.1" + SEP + "a" + SEP + "b" + SEP + "c.txt", FilenameUtils.normalize("\\\\127.0.0.1\\a\\b\\c.txt"));
251+
assertEquals(SEP + SEP + "::1" + SEP + "a" + SEP + "b" + SEP + "c.txt", FilenameUtils.normalize("\\\\::1\\a\\b\\c.txt"));
252+
assertEquals(SEP + SEP + "server.example.org" + SEP + "a" + SEP + "b" + SEP + "c.txt", FilenameUtils.normalize("\\\\server.example.org\\a\\b\\c.txt"));
253+
assertEquals(SEP + SEP + "server." + SEP + "a" + SEP + "b" + SEP + "c.txt", FilenameUtils.normalize("\\\\server.\\a\\b\\c.txt"));
254+
255+
assertEquals(null, FilenameUtils.normalize("\\\\-server\\a\\b\\c.txt"));
256+
assertEquals(null, FilenameUtils.normalize("\\\\.\\a\\b\\c.txt"));
257+
assertEquals(null, FilenameUtils.normalize("\\\\..\\a\\b\\c.txt"));
249258
}
250259

251260
@Test
@@ -562,6 +571,15 @@ public void testGetPrefixLength() {
562571
assertEquals(1, FilenameUtils.getPrefixLength("/:foo"));
563572
assertEquals(1, FilenameUtils.getPrefixLength("/:/"));
564573
assertEquals(1, FilenameUtils.getPrefixLength("/:::::::.txt"));
574+
575+
assertEquals(12, FilenameUtils.getPrefixLength("\\\\127.0.0.1\\a\\b\\c.txt"));
576+
assertEquals(6, FilenameUtils.getPrefixLength("\\\\::1\\a\\b\\c.txt"));
577+
assertEquals(21, FilenameUtils.getPrefixLength("\\\\server.example.org\\a\\b\\c.txt"));
578+
assertEquals(10, FilenameUtils.getPrefixLength("\\\\server.\\a\\b\\c.txt"));
579+
580+
assertEquals(-1, FilenameUtils.getPrefixLength("\\\\-server\\a\\b\\c.txt"));
581+
assertEquals(-1, FilenameUtils.getPrefixLength("\\\\.\\a\\b\\c.txt"));
582+
assertEquals(-1, FilenameUtils.getPrefixLength("\\\\..\\a\\b\\c.txt"));
565583
}
566584

567585
@Test

0 commit comments

Comments
 (0)