|
19 | 19 | import java.io.File; |
20 | 20 | import java.io.IOException; |
21 | 21 | import java.util.ArrayList; |
| 22 | +import java.util.Arrays; |
22 | 23 | import java.util.Collection; |
| 24 | +import java.util.List; |
23 | 25 | import java.util.Stack; |
| 26 | +import java.util.regex.Matcher; |
| 27 | +import java.util.regex.Pattern; |
24 | 28 |
|
25 | 29 | /** |
26 | 30 | * General file name and file path manipulation utilities. |
@@ -676,7 +680,9 @@ public static int getPrefixLength(final String fileName) { |
676 | 680 | } |
677 | 681 | posUnix = posUnix == NOT_FOUND ? posWin : posUnix; |
678 | 682 | 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; |
680 | 686 | } else { |
681 | 687 | return isSeparator(ch0) ? 1 : 0; |
682 | 688 | } |
@@ -1490,4 +1496,143 @@ static String[] splitOnTokens(final String text) { |
1490 | 1496 | return list.toArray( new String[ list.size() ] ); |
1491 | 1497 | } |
1492 | 1498 |
|
| 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 | + } |
1493 | 1638 | } |
0 commit comments