1 /* 2 * Copyright (c) 2013, 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 java.net; 27 28 import java.net.*; 29 import java.util.Formatter; 30 import java.util.Locale; 31 import sun.net.util.IPAddressUtil; 32 33 /** 34 * Parses a string containing a host/domain name and port range 35 */ 36 class HostPortrange { 37 38 String hostname; 39 String scheme; 40 int[] portrange; 41 42 boolean wildcard; 43 boolean literal; 44 boolean ipv6, ipv4; 45 static final int PORT_MIN = 0; 46 static final int PORT_MAX = (1 << 16) -1; 47 48 boolean equals(HostPortrange that) { 49 return this.hostname.equals(that.hostname) 50 && this.portrange[0] == that.portrange[0] 51 && this.portrange[1] == that.portrange[1] 52 && this.wildcard == that.wildcard 53 && this.literal == that.literal; 54 } 55 56 public int hashCode() { 57 return hostname.hashCode() + portrange[0] + portrange[1]; 58 } 59 60 HostPortrange(String scheme, String str) { 61 // Parse the host name. A name has up to three components, the 62 // hostname, a port number, or two numbers representing a port 63 // range. "www.example.com:8080-9090" is a valid host name. 64 65 // With IPv6 an address can be 2010:836B:4179::836B:4179 66 // An IPv6 address needs to be enclose in [] 67 // For ex: [2010:836B:4179::836B:4179]:8080-9090 68 // Refer to RFC 2732 for more information. 69 70 // first separate string into two fields: hoststr, portstr 71 String hoststr, portstr = null; 72 this.scheme = scheme; 73 74 // check for IPv6 address 75 if (str.charAt(0) == '[') { 76 ipv6 = literal = true; 77 int rb = str.indexOf(']'); 78 if (rb != -1) { 79 hoststr = str.substring(1, rb); 80 } else { 81 throw new IllegalArgumentException("invalid IPv6 address: " + str); 82 } 83 int sep = str.indexOf(':', rb + 1); 84 if (sep != -1 && str.length() > sep) { 85 portstr = str.substring(sep + 1); 86 } 87 // need to normalize hoststr now 88 byte[] ip = IPAddressUtil.textToNumericFormatV6(hoststr); 89 if (ip == null) { 90 throw new IllegalArgumentException("illegal IPv6 address"); 91 } 92 StringBuilder sb = new StringBuilder(); 93 Formatter formatter = new Formatter(sb, Locale.US); 94 formatter.format("%02x%02x:%02x%02x:%02x%02x:%02x" 95 + "%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x", 96 ip[0], ip[1], ip[2], ip[3], ip[4], ip[5], ip[6], ip[7], ip[8], 97 ip[9], ip[10], ip[11], ip[12], ip[13], ip[14], ip[15]); 98 hostname = sb.toString(); 99 } else { 100 // not IPv6 therefore ':' is the port separator 101 102 int sep = str.indexOf(':'); 103 if (sep != -1 && str.length() > sep) { 104 hoststr = str.substring(0, sep); 105 portstr = str.substring(sep + 1); 106 } else { 107 hoststr = sep == -1 ? str : str.substring(0, sep); 108 } 109 // is this a domain wildcard specification? 110 if (hoststr.lastIndexOf('*') > 0) { 111 throw new IllegalArgumentException("invalid host wildcard specification"); 112 } else if (hoststr.startsWith("*")) { 113 wildcard = true; 114 if (hoststr.equals("*")) { 115 hoststr = ""; 116 } else if (hoststr.startsWith("*.")) { 117 hoststr = toLowerCase(hoststr.substring(1)); 118 } else { 119 throw new IllegalArgumentException("invalid host wildcard specification"); 120 } 121 } else { 122 // check if ipv4 (if rightmost label a number) 123 // The normal way to specify ipv4 is 4 decimal labels 124 // but actually three, two or single label formats valid also 125 // So, we recognise ipv4 by just testing the rightmost label 126 // being a number. 127 int lastdot = hoststr.lastIndexOf('.'); 128 if (lastdot != -1 && (hoststr.length() > 1)) { 129 boolean ipv4 = true; 130 131 for (int i = lastdot + 1, len = hoststr.length(); i < len; i++) { 132 char c = hoststr.charAt(i); 133 if (c < '0' || c > '9') { 134 ipv4 = false; 135 break; 136 } 137 } 138 this.ipv4 = this.literal = ipv4; 139 if (ipv4) { 140 byte[] ip = IPAddressUtil.textToNumericFormatV4(hoststr); 141 if (ip == null) { 142 throw new IllegalArgumentException("illegal IPv4 address"); 143 } 144 StringBuilder sb = new StringBuilder(); 145 Formatter formatter = new Formatter(sb, Locale.US); 146 formatter.format("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); 147 hoststr = sb.toString(); 148 } else { 149 // regular domain name 150 hoststr = toLowerCase(hoststr); 151 } 152 } 153 } 154 hostname = hoststr; 155 } 156 157 try { 158 portrange = parsePort(portstr); 159 } catch (Exception e) { 160 throw new IllegalArgumentException("invalid port range: " + portstr); 161 } 162 } 163 164 static final int CASE_DIFF = 'A' - 'a'; 165 166 /** 167 * Convert to lower case, and check that all chars are ascii 168 * alphanumeric, '-' or '.' only. 169 */ 170 static String toLowerCase(String s) { 171 int len = s.length(); 172 StringBuilder sb = null; 173 174 for (int i=0; i<len; i++) { 175 char c = s.charAt(i); 176 if ((c >= 'a' && c <= 'z') || (c == '.')) { 177 if (sb != null) 178 sb.append(c); 179 } else if ((c >= '0' && c <= '9') || (c == '-')) { 180 if (sb != null) 181 sb.append(c); 182 } else if (c >= 'A' && c <= 'Z') { 183 if (sb == null) { 184 sb = new StringBuilder(len); 185 sb.append(s, 0, i); 186 } 187 sb.append((char)(c - CASE_DIFF)); 188 } else { 189 throw new IllegalArgumentException("Invalid characters in hostname"); 190 } 191 } 192 return sb == null ? s : sb.toString(); 193 } 194 195 196 public boolean literal() { 197 return literal; 198 } 199 200 public boolean ipv4Literal() { 201 return ipv4; 202 } 203 204 public boolean ipv6Literal() { 205 return ipv6; 206 } 207 208 public String hostname() { 209 return hostname; 210 } 211 212 public int[] portrange() { 213 return portrange; 214 } 215 216 /** 217 * returns true if the hostname part started with * 218 * hostname returns the remaining part of the host component 219 * eg "*.foo.com" -> ".foo.com" or "*" -> "" 220 * 221 * @return 222 */ 223 public boolean wildcard() { 224 return wildcard; 225 } 226 227 // these shouldn't leak outside the implementation 228 static final int[] HTTP_PORT = {80, 80}; 229 static final int[] HTTPS_PORT = {443, 443}; 230 static final int[] NO_PORT = {-1, -1}; 231 232 int[] defaultPort() { 233 if (scheme.equals("http")) { 234 return HTTP_PORT; 235 } else if (scheme.equals("https")) { 236 return HTTPS_PORT; 237 } 238 return NO_PORT; 239 } 240 241 int[] parsePort(String port) 242 { 243 244 if (port == null || port.isEmpty()) { 245 return defaultPort(); 246 } 247 248 if (port.equals("*")) { 249 return new int[] {PORT_MIN, PORT_MAX}; 250 } 251 252 try { 253 int dash = port.indexOf('-'); 254 255 if (dash == -1) { 256 int p = Integer.parseInt(port); 257 return new int[] {p, p}; 258 } else { 259 String low = port.substring(0, dash); 260 String high = port.substring(dash+1); 261 int l,h; 262 263 if (low.isEmpty()) { 264 l = PORT_MIN; 265 } else { 266 l = Integer.parseInt(low); 267 } 268 269 if (high.isEmpty()) { 270 h = PORT_MAX; 271 } else { 272 h = Integer.parseInt(high); 273 } 274 if (l < 0 || h < 0 || h<l) { 275 return defaultPort(); 276 } 277 return new int[] {l, h}; 278 } 279 } catch (IllegalArgumentException e) { 280 return defaultPort(); 281 } 282 } 283 }