1
1
/*
2
- * Copyright (c) 2004, 2021 , Oracle and/or its affiliates. All rights reserved.
2
+ * Copyright (c) 2004, 2022 , Oracle and/or its affiliates. All rights reserved.
3
3
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4
4
*
5
5
* This code is free software; you can redistribute it and/or modify it
25
25
26
26
package sun .net .util ;
27
27
28
- import java .io .IOException ;
28
+ import sun .security .action .GetPropertyAction ;
29
+
29
30
import java .io .UncheckedIOException ;
30
31
import java .net .Inet6Address ;
31
32
import java .net .InetAddress ;
32
33
import java .net .InetSocketAddress ;
33
34
import java .net .NetworkInterface ;
34
35
import java .net .SocketException ;
35
36
import java .net .URL ;
37
+ import java .nio .CharBuffer ;
36
38
import java .security .AccessController ;
37
39
import java .security .PrivilegedExceptionAction ;
38
40
import java .security .PrivilegedActionException ;
@@ -100,7 +102,7 @@ public static byte[] textToNumericFormatV4(String src)
100
102
tmpValue = 0 ;
101
103
newOctet = true ;
102
104
} else {
103
- int digit = Character . digit (c , 10 );
105
+ int digit = digit (c , 10 );
104
106
if (digit < 0 ) {
105
107
return null ;
106
108
}
@@ -125,6 +127,29 @@ public static byte[] textToNumericFormatV4(String src)
125
127
return res ;
126
128
}
127
129
130
+ /**
131
+ * Validates if input string is a valid IPv4 address literal.
132
+ * If the "jdk.net.allowAmbiguousIPAddressLiterals" system property is set
133
+ * to {@code false}, or is not set then validation of the address string is performed as follows:
134
+ * If string can't be parsed by following IETF IPv4 address string literals
135
+ * formatting style rules (default one), but can be parsed by following BSD formatting
136
+ * style rules, the IPv4 address string content is treated as ambiguous and
137
+ * {@code IllegalArgumentException} is thrown.
138
+ *
139
+ * @param src input string
140
+ * @return bytes array if string is a valid IPv4 address string
141
+ * @throws IllegalArgumentException if "jdk.net.allowAmbiguousIPAddressLiterals" SP is set to
142
+ * "false" and IPv4 address string {@code "src"} is ambiguous
143
+ */
144
+ public static byte [] validateNumericFormatV4 (String src ) {
145
+ byte [] parsedBytes = textToNumericFormatV4 (src );
146
+ if (!ALLOW_AMBIGUOUS_IPADDRESS_LITERALS_SP_VALUE
147
+ && parsedBytes == null && isBsdParsableV4 (src )) {
148
+ throw new IllegalArgumentException ("Invalid IP address literal: " + src );
149
+ }
150
+ return parsedBytes ;
151
+ }
152
+
128
153
/*
129
154
* Convert IPv6 presentation level address to network order binary form.
130
155
* credit:
@@ -170,7 +195,7 @@ public static byte[] textToNumericFormatV6(String src)
170
195
val = 0 ;
171
196
while (i < srcb_length ) {
172
197
ch = srcb [i ++];
173
- int chval = Character . digit (ch , 16 );
198
+ int chval = digit (ch , 16 );
174
199
if (chval != -1 ) {
175
200
val <<= 4 ;
176
201
val |= chval ;
@@ -551,4 +576,245 @@ public static String checkHostString(String host) {
551
576
}
552
577
return null ;
553
578
}
579
+
580
+ /**
581
+ * Returns the numeric value of the character {@code ch} in the
582
+ * specified radix.
583
+ *
584
+ * @param ch the character to be converted.
585
+ * @param radix the radix.
586
+ * @return the numeric value represented by the character in the
587
+ * specified radix.
588
+ */
589
+ public static int digit (char ch , int radix ) {
590
+ if (ALLOW_AMBIGUOUS_IPADDRESS_LITERALS_SP_VALUE ) {
591
+ return Character .digit (ch , radix );
592
+ } else {
593
+ return parseAsciiDigit (ch , radix );
594
+ }
595
+ }
596
+
597
+ /**
598
+ * Try to parse String as IPv4 address literal by following
599
+ * BSD-style formatting rules.
600
+ *
601
+ * @param input input string
602
+ * @return {@code true} if input string is parsable as IPv4 address literal,
603
+ * {@code false} otherwise.
604
+ */
605
+ public static boolean isBsdParsableV4 (String input ) {
606
+ char firstSymbol = input .charAt (0 );
607
+ // Check if first digit is not a decimal digit
608
+ if (parseAsciiDigit (firstSymbol , DECIMAL ) == -1 ) {
609
+ return false ;
610
+ }
611
+
612
+ // Last character is dot OR is not a supported digit: [0-9,A-F,a-f]
613
+ char lastSymbol = input .charAt (input .length () - 1 );
614
+ if (lastSymbol == '.' || parseAsciiHexDigit (lastSymbol ) == -1 ) {
615
+ return false ;
616
+ }
617
+
618
+ // Parse IP address fields
619
+ CharBuffer charBuffer = CharBuffer .wrap (input );
620
+ int fieldNumber = 0 ;
621
+ while (charBuffer .hasRemaining ()) {
622
+ long fieldValue = -1L ;
623
+ // Try to parse fields in all supported radixes
624
+ for (int radix : SUPPORTED_RADIXES ) {
625
+ fieldValue = parseV4FieldBsd (radix , charBuffer , fieldNumber );
626
+ if (fieldValue >= 0 ) {
627
+ fieldNumber ++;
628
+ break ;
629
+ } else if (fieldValue == TERMINAL_PARSE_ERROR ) {
630
+ return false ;
631
+ }
632
+ }
633
+ // If field can't be parsed as one of supported radixes stop
634
+ // parsing
635
+ if (fieldValue < 0 ) {
636
+ return false ;
637
+ }
638
+ }
639
+ return true ;
640
+ }
641
+
642
+ /**
643
+ * Method tries to parse IP address field that starts from {@linkplain CharBuffer#position()
644
+ * current position} of the provided character buffer.
645
+ * <p>
646
+ * This method supports three {@code "radix"} values to decode field values in
647
+ * {@code "HEXADECIMAL (radix=16)"}, {@code "DECIMAL (radix=10)"} and
648
+ * {@code "OCTAL (radix=8)"} radixes.
649
+ * <p>
650
+ * If {@code -1} value is returned the char buffer position is reset to the value
651
+ * it was before it was called.
652
+ * <p>
653
+ * Method returns {@code -2} if formatting illegal for all supported {@code radix}
654
+ * values is observed, and there is no point in checking other radix values.
655
+ * That includes the following cases:<ul>
656
+ * <li>Two subsequent dots are observer
657
+ * <li>Number of dots more than 3
658
+ * <li>Field value exceeds max allowed
659
+ * <li>Character is not a valid digit for the requested {@code radix} value, given
660
+ * that a field has the radix specific prefix
661
+ * </ul>
662
+ *
663
+ * @param radix digits encoding radix to use for parsing. Valid values: 8, 10, 16.
664
+ * @param buffer {@code CharBuffer} with position set to the field's fist character
665
+ * @param fieldNumber parsed field number
666
+ * @return {@code CANT_PARSE_IN_RADIX} if field can not be parsed in requested {@code radix}.
667
+ * {@code TERMINAL_PARSE_ERROR} if field can't be parsed and the whole parse process should be terminated.
668
+ * Parsed field value otherwise.
669
+ */
670
+ private static long parseV4FieldBsd (int radix , CharBuffer buffer , int fieldNumber ) {
671
+ int initialPos = buffer .position ();
672
+ long val = 0 ;
673
+ int digitsCount = 0 ;
674
+ if (!checkPrefix (buffer , radix )) {
675
+ val = CANT_PARSE_IN_RADIX ;
676
+ }
677
+ boolean dotSeen = false ;
678
+ while (buffer .hasRemaining () && val != CANT_PARSE_IN_RADIX && !dotSeen ) {
679
+ char c = buffer .get ();
680
+ if (c == '.' ) {
681
+ dotSeen = true ;
682
+ // Fail if 4 dots in IP address string.
683
+ // fieldNumber counter starts from 0, therefore 3
684
+ if (fieldNumber == 3 ) {
685
+ // Terminal state, can stop parsing: too many fields
686
+ return TERMINAL_PARSE_ERROR ;
687
+ }
688
+ // Check for literals with two dots, like '1.2..3', '1.2.3..'
689
+ if (digitsCount == 0 ) {
690
+ // Terminal state, can stop parsing: dot with no digits
691
+ return TERMINAL_PARSE_ERROR ;
692
+ }
693
+ if (val > 255 ) {
694
+ // Terminal state, can stop parsing: too big value for an octet
695
+ return TERMINAL_PARSE_ERROR ;
696
+ }
697
+ } else {
698
+ int dv = parseAsciiDigit (c , radix );
699
+ if (dv >= 0 ) {
700
+ digitsCount ++;
701
+ val *= radix ;
702
+ val += dv ;
703
+ } else {
704
+ // Spotted digit can't be parsed in the requested 'radix'.
705
+ // The order in which radixes are checked - hex, octal, decimal:
706
+ // - if symbol is not a valid digit in hex radix - terminal
707
+ // - if symbol is not a valid digit in octal radix, and given
708
+ // that octal prefix was observed before - terminal
709
+ // - if symbol is not a valid digit in decimal radix - terminal
710
+ return TERMINAL_PARSE_ERROR ;
711
+ }
712
+ }
713
+ }
714
+ if (val == CANT_PARSE_IN_RADIX ) {
715
+ buffer .position (initialPos );
716
+ } else if (!dotSeen ) {
717
+ // It is the last field - check its value
718
+ // This check will ensure that address strings with less
719
+ // than 4 fields, i.e. A, A.B and A.B.C address types
720
+ // contain value less then the allowed maximum for the last field.
721
+ long maxValue = (1L << ((4 - fieldNumber ) * 8 )) - 1 ;
722
+ if (val > maxValue ) {
723
+ // Terminal state, can stop parsing: last field value exceeds its
724
+ // allowed value
725
+ return TERMINAL_PARSE_ERROR ;
726
+ }
727
+ }
728
+ return val ;
729
+ }
730
+
731
+ // This method moves the position of the supplied CharBuffer by analysing the digit prefix
732
+ // symbols if any.
733
+ // The caller should reset the position when method returns false.
734
+ private static boolean checkPrefix (CharBuffer buffer , int radix ) {
735
+ return switch (radix ) {
736
+ case OCTAL -> isOctalFieldStart (buffer );
737
+ case DECIMAL -> isDecimalFieldStart (buffer );
738
+ case HEXADECIMAL -> isHexFieldStart (buffer );
739
+ default -> throw new AssertionError ("Not supported radix" );
740
+ };
741
+ }
742
+
743
+ // This method always moves the position of the supplied CharBuffer
744
+ // removing the octal prefix symbols '0'.
745
+ // The caller should reset the position when method returns false.
746
+ private static boolean isOctalFieldStart (CharBuffer cb ) {
747
+ // .0<EOS> is not treated as octal field
748
+ if (cb .remaining () < 2 ) {
749
+ return false ;
750
+ }
751
+
752
+ // Fetch two first characters
753
+ int position = cb .position ();
754
+ char first = cb .get ();
755
+ char second = cb .get ();
756
+
757
+ // Return false if the first char is not octal prefix '0' or second is a
758
+ // field separator - parseV4FieldBsd will reset position to start of the field.
759
+ // '.0.' fields will be successfully parsed in decimal radix.
760
+ boolean isOctalPrefix = first == '0' && second != '.' ;
761
+
762
+ // If the prefix looks like octal - consume '0', otherwise 'false' is returned
763
+ // and caller will reset the buffer position.
764
+ if (isOctalPrefix ) {
765
+ cb .position (position + 1 );
766
+ }
767
+ return isOctalPrefix ;
768
+ }
769
+
770
+ // This method doesn't move the position of the supplied CharBuffer
771
+ private static boolean isDecimalFieldStart (CharBuffer cb ) {
772
+ return cb .hasRemaining ();
773
+ }
774
+
775
+ // This method always moves the position of the supplied CharBuffer
776
+ // removing the hexadecimal prefix symbols '0x'.
777
+ // The caller should reset the position when method returns false.
778
+ private static boolean isHexFieldStart (CharBuffer cb ) {
779
+ if (cb .remaining () < 2 ) {
780
+ return false ;
781
+ }
782
+ char first = cb .get ();
783
+ char second = cb .get ();
784
+ return first == '0' && (second == 'x' || second == 'X' );
785
+ }
786
+
787
+ // Parse ASCII digit in given radix
788
+ private static int parseAsciiDigit (char c , int radix ) {
789
+ assert radix == OCTAL || radix == DECIMAL || radix == HEXADECIMAL ;
790
+ if (radix == HEXADECIMAL ) {
791
+ return parseAsciiHexDigit (c );
792
+ }
793
+ int val = c - '0' ;
794
+ return (val < 0 || val >= radix ) ? -1 : val ;
795
+ }
796
+
797
+ // Parse ASCII digit in hexadecimal radix
798
+ private static int parseAsciiHexDigit (char digit ) {
799
+ char c = Character .toLowerCase (digit );
800
+ if (c >= 'a' && c <= 'f' ) {
801
+ return c - 'a' + 10 ;
802
+ }
803
+ return parseAsciiDigit (c , DECIMAL );
804
+ }
805
+
806
+ // Supported radixes
807
+ private static final int HEXADECIMAL = 16 ;
808
+ private static final int DECIMAL = 10 ;
809
+ private static final int OCTAL = 8 ;
810
+ // Order in which field formats are exercised to parse one IP address textual field
811
+ private static final int [] SUPPORTED_RADIXES = new int []{HEXADECIMAL , OCTAL , DECIMAL };
812
+
813
+ // BSD parser's return values
814
+ private final static long CANT_PARSE_IN_RADIX = -1L ;
815
+ private final static long TERMINAL_PARSE_ERROR = -2L ;
816
+
817
+ private static final String ALLOW_AMBIGUOUS_IPADDRESS_LITERALS_SP = "jdk.net.allowAmbiguousIPAddressLiterals" ;
818
+ private static final boolean ALLOW_AMBIGUOUS_IPADDRESS_LITERALS_SP_VALUE = Boolean .valueOf (
819
+ GetPropertyAction .privilegedGetProperty (ALLOW_AMBIGUOUS_IPADDRESS_LITERALS_SP , "false" ));
554
820
}
0 commit comments