1 /* 2 * Copyright (c) 2004, 2020, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.net.util; 27 28 import java.io.IOException; 29 import java.io.UncheckedIOException; 30 import java.net.Inet6Address; 31 import java.net.InetAddress; 32 import java.net.InetSocketAddress; 33 import java.net.NetworkInterface; 34 import java.net.SocketException; 35 import java.net.URL; 36 import java.security.AccessController; 37 import java.security.PrivilegedExceptionAction; 38 import java.security.PrivilegedActionException; 39 import java.util.Arrays; 40 import java.util.List; 41 import java.util.concurrent.ConcurrentHashMap; 42 import java.util.stream.Collectors; 43 44 public class IPAddressUtil { 45 private static final int INADDR4SZ = 4; 46 private static final int INADDR16SZ = 16; 47 private static final int INT16SZ = 2; 48 49 /* 50 * Converts IPv4 address in its textual presentation form 51 * into its numeric binary form. 52 * 53 * @param src a String representing an IPv4 address in standard format 54 * @return a byte array representing the IPv4 numeric address 55 */ 56 @SuppressWarnings("fallthrough") 57 public static byte[] textToNumericFormatV4(String src) 58 { 59 byte[] res = new byte[INADDR4SZ]; 60 61 long tmpValue = 0; 62 int currByte = 0; 63 boolean newOctet = true; 64 65 int len = src.length(); 66 if (len == 0 || len > 15) { 67 return null; 68 } 69 /* 70 * When only one part is given, the value is stored directly in 71 * the network address without any byte rearrangement. 72 * 73 * When a two part address is supplied, the last part is 74 * interpreted as a 24-bit quantity and placed in the right 75 * most three bytes of the network address. This makes the 76 * two part address format convenient for specifying Class A 77 * network addresses as net.host. 78 * 79 * When a three part address is specified, the last part is 80 * interpreted as a 16-bit quantity and placed in the right 81 * most two bytes of the network address. This makes the 82 * three part address format convenient for specifying 83 * Class B net- work addresses as 128.net.host. 84 * 85 * When four parts are specified, each is interpreted as a 86 * byte of data and assigned, from left to right, to the 87 * four bytes of an IPv4 address. 88 * 89 * We determine and parse the leading parts, if any, as single 90 * byte values in one pass directly into the resulting byte[], 91 * then the remainder is treated as a 8-to-32-bit entity and 92 * translated into the remaining bytes in the array. 93 */ 94 for (int i = 0; i < len; i++) { 95 char c = src.charAt(i); 96 if (c == '.') { 97 if (newOctet || tmpValue < 0 || tmpValue > 0xff || currByte == 3) { 98 return null; 99 } 100 res[currByte++] = (byte) (tmpValue & 0xff); 101 tmpValue = 0; 102 newOctet = true; 103 } else { 104 int digit = Character.digit(c, 10); 105 if (digit < 0) { 106 return null; 107 } 108 tmpValue *= 10; 109 tmpValue += digit; 110 newOctet = false; 111 } 112 } 113 if (newOctet || tmpValue < 0 || tmpValue >= (1L << ((4 - currByte) * 8))) { 114 return null; 115 } 116 switch (currByte) { 117 case 0: 118 res[0] = (byte) ((tmpValue >> 24) & 0xff); 119 case 1: 120 res[1] = (byte) ((tmpValue >> 16) & 0xff); 121 case 2: 122 res[2] = (byte) ((tmpValue >> 8) & 0xff); 123 case 3: 124 res[3] = (byte) ((tmpValue >> 0) & 0xff); 125 } 126 return res; 127 } 128 129 /* 130 * Convert IPv6 presentation level address to network order binary form. 131 * credit: 132 * Converted from C code from Solaris 8 (inet_pton) 133 * 134 * Any component of the string following a per-cent % is ignored. 135 * 136 * @param src a String representing an IPv6 address in textual format 137 * @return a byte array representing the IPv6 numeric address 138 */ 139 public static byte[] textToNumericFormatV6(String src) 140 { 141 // Shortest valid string is "::", hence at least 2 chars 142 if (src.length() < 2) { 143 return null; 144 } 145 146 int colonp; 147 char ch; 148 boolean saw_xdigit; 149 int val; 150 char[] srcb = src.toCharArray(); 151 byte[] dst = new byte[INADDR16SZ]; 152 153 int srcb_length = srcb.length; 154 int pc = src.indexOf ('%'); 155 if (pc == srcb_length -1) { 156 return null; 157 } 158 159 if (pc != -1) { 160 srcb_length = pc; 161 } 162 163 colonp = -1; 164 int i = 0, j = 0; 165 /* Leading :: requires some special handling. */ 166 if (srcb[i] == ':') 167 if (srcb[++i] != ':') 168 return null; 169 int curtok = i; 170 saw_xdigit = false; 171 val = 0; 172 while (i < srcb_length) { 173 ch = srcb[i++]; 174 int chval = Character.digit(ch, 16); 175 if (chval != -1) { 176 val <<= 4; 177 val |= chval; 178 if (val > 0xffff) 179 return null; 180 saw_xdigit = true; 181 continue; 182 } 183 if (ch == ':') { 184 curtok = i; 185 if (!saw_xdigit) { 186 if (colonp != -1) 187 return null; 188 colonp = j; 189 continue; 190 } else if (i == srcb_length) { 191 return null; 192 } 193 if (j + INT16SZ > INADDR16SZ) 194 return null; 195 dst[j++] = (byte) ((val >> 8) & 0xff); 196 dst[j++] = (byte) (val & 0xff); 197 saw_xdigit = false; 198 val = 0; 199 continue; 200 } 201 if (ch == '.' && ((j + INADDR4SZ) <= INADDR16SZ)) { 202 String ia4 = src.substring(curtok, srcb_length); 203 /* check this IPv4 address has 3 dots, i.e. A.B.C.D */ 204 int dot_count = 0, index=0; 205 while ((index = ia4.indexOf ('.', index)) != -1) { 206 dot_count ++; 207 index ++; 208 } 209 if (dot_count != 3) { 210 return null; 211 } 212 byte[] v4addr = textToNumericFormatV4(ia4); 213 if (v4addr == null) { 214 return null; 215 } 216 for (int k = 0; k < INADDR4SZ; k++) { 217 dst[j++] = v4addr[k]; 218 } 219 saw_xdigit = false; 220 break; /* '\0' was seen by inet_pton4(). */ 221 } 222 return null; 223 } 224 if (saw_xdigit) { 225 if (j + INT16SZ > INADDR16SZ) 226 return null; 227 dst[j++] = (byte) ((val >> 8) & 0xff); 228 dst[j++] = (byte) (val & 0xff); 229 } 230 231 if (colonp != -1) { 232 int n = j - colonp; 233 234 if (j == INADDR16SZ) 235 return null; 236 for (i = 1; i <= n; i++) { 237 dst[INADDR16SZ - i] = dst[colonp + n - i]; 238 dst[colonp + n - i] = 0; 239 } 240 j = INADDR16SZ; 241 } 242 if (j != INADDR16SZ) 243 return null; 244 byte[] newdst = convertFromIPv4MappedAddress(dst); 245 if (newdst != null) { 246 return newdst; 247 } else { 248 return dst; 249 } 250 } 251 252 /** 253 * @param src a String representing an IPv4 address in textual format 254 * @return a boolean indicating whether src is an IPv4 literal address 255 */ 256 public static boolean isIPv4LiteralAddress(String src) { 257 return textToNumericFormatV4(src) != null; 258 } 259 260 /** 261 * @param src a String representing an IPv6 address in textual format 262 * @return a boolean indicating whether src is an IPv6 literal address 263 */ 264 public static boolean isIPv6LiteralAddress(String src) { 265 return textToNumericFormatV6(src) != null; 266 } 267 268 /* 269 * Convert IPv4-Mapped address to IPv4 address. Both input and 270 * returned value are in network order binary form. 271 * 272 * @param src a String representing an IPv4-Mapped address in textual format 273 * @return a byte array representing the IPv4 numeric address 274 */ 275 public static byte[] convertFromIPv4MappedAddress(byte[] addr) { 276 if (isIPv4MappedAddress(addr)) { 277 byte[] newAddr = new byte[INADDR4SZ]; 278 System.arraycopy(addr, 12, newAddr, 0, INADDR4SZ); 279 return newAddr; 280 } 281 return null; 282 } 283 284 /** 285 * Utility routine to check if the InetAddress is an 286 * IPv4 mapped IPv6 address. 287 * 288 * @return a <code>boolean</code> indicating if the InetAddress is 289 * an IPv4 mapped IPv6 address; or false if address is IPv4 address. 290 */ 291 private static boolean isIPv4MappedAddress(byte[] addr) { 292 if (addr.length < INADDR16SZ) { 293 return false; 294 } 295 if ((addr[0] == 0x00) && (addr[1] == 0x00) && 296 (addr[2] == 0x00) && (addr[3] == 0x00) && 297 (addr[4] == 0x00) && (addr[5] == 0x00) && 298 (addr[6] == 0x00) && (addr[7] == 0x00) && 299 (addr[8] == 0x00) && (addr[9] == 0x00) && 300 (addr[10] == (byte)0xff) && 301 (addr[11] == (byte)0xff)) { 302 return true; 303 } 304 return false; 305 } 306 /** 307 * Mapping from unscoped local Inet(6)Address to the same address 308 * including the correct scope-id, determined from NetworkInterface. 309 */ 310 private final static ConcurrentHashMap<InetAddress,InetAddress> 311 cache = new ConcurrentHashMap<>(); 312 313 /** 314 * Returns a scoped version of the supplied local, link-local ipv6 address 315 * if that scope-id can be determined from local NetworkInterfaces. 316 * If the address already has a scope-id or if the address is not local, ipv6 317 * or link local, then the original address is returned. 318 * 319 * @param address 320 * @exception SocketException if the given ipv6 link local address is found 321 * on more than one local interface 322 * @return 323 */ 324 public static InetAddress toScopedAddress(InetAddress address) 325 throws SocketException { 326 327 if (address instanceof Inet6Address && address.isLinkLocalAddress() 328 && ((Inet6Address) address).getScopeId() == 0) { 329 330 InetAddress cached = null; 331 try { 332 cached = cache.computeIfAbsent(address, k -> findScopedAddress(k)); 333 } catch (UncheckedIOException e) { 334 throw (SocketException)e.getCause(); 335 } 336 return cached != null ? cached : address; 337 } else { 338 return address; 339 } 340 } 341 342 /** 343 * Same as above for InetSocketAddress 344 */ 345 public static InetSocketAddress toScopedAddress(InetSocketAddress address) 346 throws SocketException { 347 InetAddress addr; 348 InetAddress orig = address.getAddress(); 349 if ((addr = toScopedAddress(orig)) == orig) { 350 return address; 351 } else { 352 return new InetSocketAddress(addr, address.getPort()); 353 } 354 } 355 356 private static InetAddress findScopedAddress(InetAddress address) { 357 PrivilegedExceptionAction<List<InetAddress>> pa = () -> NetworkInterface.networkInterfaces() 358 .flatMap(NetworkInterface::inetAddresses) 359 .filter(a -> (a instanceof Inet6Address) 360 && address.equals(a) 361 && ((Inet6Address) a).getScopeId() != 0) 362 .collect(Collectors.toList()); 363 List<InetAddress> result; 364 try { 365 result = AccessController.doPrivileged(pa); 366 var sz = result.size(); 367 if (sz == 0) 368 return null; 369 if (sz > 1) 370 throw new UncheckedIOException(new SocketException( 371 "Duplicate link local addresses: must specify scope-id")); 372 return result.get(0); 373 } catch (PrivilegedActionException pae) { 374 return null; 375 } 376 } 377 378 // See java.net.URI for more details on how to generate these 379 // masks. 380 // 381 // square brackets 382 private static final long L_IPV6_DELIMS = 0x0L; // "[]" 383 private static final long H_IPV6_DELIMS = 0x28000000L; // "[]" 384 // RFC 3986 gen-delims 385 private static final long L_GEN_DELIMS = 0x8400800800000000L; // ":/?#[]@" 386 private static final long H_GEN_DELIMS = 0x28000001L; // ":/?#[]@" 387 // These gen-delims can appear in authority 388 private static final long L_AUTH_DELIMS = 0x400000000000000L; // "@[]:" 389 private static final long H_AUTH_DELIMS = 0x28000001L; // "@[]:" 390 // colon is allowed in userinfo 391 private static final long L_COLON = 0x400000000000000L; // ":" 392 private static final long H_COLON = 0x0L; // ":" 393 // slash should be encoded in authority 394 private static final long L_SLASH = 0x800000000000L; // "/" 395 private static final long H_SLASH = 0x0L; // "/" 396 // backslash should always be encoded 397 private static final long L_BACKSLASH = 0x0L; // "\" 398 private static final long H_BACKSLASH = 0x10000000L; // "\" 399 // ASCII chars 0-31 + 127 - various controls + CRLF + TAB 400 private static final long L_NON_PRINTABLE = 0xffffffffL; 401 private static final long H_NON_PRINTABLE = 0x8000000000000000L; 402 // All of the above 403 private static final long L_EXCLUDE = 0x84008008ffffffffL; 404 private static final long H_EXCLUDE = 0x8000000038000001L; 405 406 private static final char[] OTHERS = { 407 8263,8264,8265,8448,8449,8453,8454,10868, 408 65109,65110,65119,65131,65283,65295,65306,65311,65312 409 }; 410 411 // Tell whether the given character is found by the given mask pair 412 public static boolean match(char c, long lowMask, long highMask) { 413 if (c < 64) 414 return ((1L << c) & lowMask) != 0; 415 if (c < 128) 416 return ((1L << (c - 64)) & highMask) != 0; 417 return false; // other non ASCII characters are not filtered 418 } 419 420 // returns -1 if the string doesn't contain any characters 421 // from the mask, the index of the first such character found 422 // otherwise. 423 public static int scan(String s, long lowMask, long highMask) { 424 int i = -1, len; 425 if (s == null || (len = s.length()) == 0) return -1; 426 boolean match = false; 427 while (++i < len && !(match = match(s.charAt(i), lowMask, highMask))); 428 if (match) return i; 429 return -1; 430 } 431 432 public static int scan(String s, long lowMask, long highMask, char[] others) { 433 int i = -1, len; 434 if (s == null || (len = s.length()) == 0) return -1; 435 boolean match = false; 436 char c, c0 = others[0]; 437 while (++i < len && !(match = match((c=s.charAt(i)), lowMask, highMask))) { 438 if (c >= c0 && (Arrays.binarySearch(others, c) > -1)) { 439 match = true; break; 440 } 441 } 442 if (match) return i; 443 444 return -1; 445 } 446 447 private static String describeChar(char c) { 448 if (c < 32 || c == 127) { 449 if (c == '\n') return "LF"; 450 if (c == '\r') return "CR"; 451 return "control char (code=" + (int)c + ")"; 452 } 453 if (c == '\\') return "'\\'"; 454 return "'" + c + "'"; 455 } 456 457 private static String checkUserInfo(String str) { 458 // colon is permitted in user info 459 int index = scan(str, L_EXCLUDE & ~L_COLON, 460 H_EXCLUDE & ~H_COLON); 461 if (index >= 0) { 462 return "Illegal character found in user-info: " 463 + describeChar(str.charAt(index)); 464 } 465 return null; 466 } 467 468 private static String checkHost(String str) { 469 int index; 470 if (str.startsWith("[") && str.endsWith("]")) { 471 str = str.substring(1, str.length() - 1); 472 if (isIPv6LiteralAddress(str)) { 473 index = str.indexOf('%'); 474 if (index >= 0) { 475 index = scan(str = str.substring(index), 476 L_NON_PRINTABLE | L_IPV6_DELIMS, 477 H_NON_PRINTABLE | H_IPV6_DELIMS); 478 if (index >= 0) { 479 return "Illegal character found in IPv6 scoped address: " 480 + describeChar(str.charAt(index)); 481 } 482 } 483 return null; 484 } 485 return "Unrecognized IPv6 address format"; 486 } else { 487 index = scan(str, L_EXCLUDE, H_EXCLUDE); 488 if (index >= 0) { 489 return "Illegal character found in host: " 490 + describeChar(str.charAt(index)); 491 } 492 } 493 return null; 494 } 495 496 private static String checkAuth(String str) { 497 int index = scan(str, 498 L_EXCLUDE & ~L_AUTH_DELIMS, 499 H_EXCLUDE & ~H_AUTH_DELIMS); 500 if (index >= 0) { 501 return "Illegal character found in authority: " 502 + describeChar(str.charAt(index)); 503 } 504 return null; 505 } 506 507 // check authority of hierarchical URL. Appropriate for 508 // HTTP-like protocol handlers 509 public static String checkAuthority(URL url) { 510 String s, u, h; 511 if (url == null) return null; 512 if ((s = checkUserInfo(u = url.getUserInfo())) != null) { 513 return s; 514 } 515 if ((s = checkHost(h = url.getHost())) != null) { 516 return s; 517 } 518 if (h == null && u == null) { 519 return checkAuth(url.getAuthority()); 520 } 521 return null; 522 } 523 524 // minimal syntax checks - deeper check may be performed 525 // by the appropriate protocol handler 526 public static String checkExternalForm(URL url) { 527 String s; 528 if (url == null) return null; 529 int index = scan(s = url.getUserInfo(), 530 L_NON_PRINTABLE | L_SLASH, 531 H_NON_PRINTABLE | H_SLASH); 532 if (index >= 0) { 533 return "Illegal character found in authority: " 534 + describeChar(s.charAt(index)); 535 } 536 if ((s = checkHostString(url.getHost())) != null) { 537 return s; 538 } 539 return null; 540 } 541 542 public static String checkHostString(String host) { 543 if (host == null) return null; 544 int index = scan(host, 545 L_NON_PRINTABLE | L_SLASH, 546 H_NON_PRINTABLE | H_SLASH, 547 OTHERS); 548 if (index >= 0) { 549 return "Illegal character found in host: " 550 + describeChar(host.charAt(index)); 551 } 552 return null; 553 } 554 }