Path: blob/master/src/java.base/share/classes/java/net/HostPortrange.java
41152 views
/*1* Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/2425package java.net;2627import java.net.*;28import java.util.Formatter;29import java.util.Locale;30import sun.net.util.IPAddressUtil;3132/**33* Parses a string containing a host/domain name and port range34*/35class HostPortrange {3637String hostname;38String scheme;39int[] portrange;4041boolean wildcard;42boolean literal;43boolean ipv6, ipv4;44static final int PORT_MIN = 0;45static final int PORT_MAX = (1 << 16) -1;4647boolean equals(HostPortrange that) {48return this.hostname.equals(that.hostname)49&& this.portrange[0] == that.portrange[0]50&& this.portrange[1] == that.portrange[1]51&& this.wildcard == that.wildcard52&& this.literal == that.literal;53}5455public int hashCode() {56return hostname.hashCode() + portrange[0] + portrange[1];57}5859HostPortrange(String scheme, String str) {60// Parse the host name. A name has up to three components, the61// hostname, a port number, or two numbers representing a port62// range. "www.example.com:8080-9090" is a valid host name.6364// With IPv6 an address can be 2010:836B:4179::836B:417965// An IPv6 address needs to be enclose in []66// For ex: [2010:836B:4179::836B:4179]:8080-909067// Refer to RFC 2732 for more information.6869// first separate string into two fields: hoststr, portstr70String hoststr, portstr = null;71this.scheme = scheme;7273// check for IPv6 address74if (str.charAt(0) == '[') {75ipv6 = literal = true;76int rb = str.indexOf(']');77if (rb != -1) {78hoststr = str.substring(1, rb);79} else {80throw new IllegalArgumentException("invalid IPv6 address: " + str);81}82int sep = str.indexOf(':', rb + 1);83if (sep != -1 && str.length() > sep) {84portstr = str.substring(sep + 1);85}86// need to normalize hoststr now87byte[] ip = IPAddressUtil.textToNumericFormatV6(hoststr);88if (ip == null) {89throw new IllegalArgumentException("illegal IPv6 address");90}91StringBuilder sb = new StringBuilder();92Formatter formatter = new Formatter(sb, Locale.US);93formatter.format("%02x%02x:%02x%02x:%02x%02x:%02x"94+ "%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",95ip[0], ip[1], ip[2], ip[3], ip[4], ip[5], ip[6], ip[7], ip[8],96ip[9], ip[10], ip[11], ip[12], ip[13], ip[14], ip[15]);97hostname = sb.toString();98} else {99// not IPv6 therefore ':' is the port separator100101int sep = str.indexOf(':');102if (sep != -1 && str.length() > sep) {103hoststr = str.substring(0, sep);104portstr = str.substring(sep + 1);105} else {106hoststr = sep == -1 ? str : str.substring(0, sep);107}108// is this a domain wildcard specification?109if (hoststr.lastIndexOf('*') > 0) {110throw new IllegalArgumentException("invalid host wildcard specification");111} else if (hoststr.startsWith("*")) {112wildcard = true;113if (hoststr.equals("*")) {114hoststr = "";115} else if (hoststr.startsWith("*.")) {116hoststr = toLowerCase(hoststr.substring(1));117} else {118throw new IllegalArgumentException("invalid host wildcard specification");119}120} else {121// check if ipv4 (if rightmost label a number)122// The normal way to specify ipv4 is 4 decimal labels123// but actually three, two or single label formats valid also124// So, we recognise ipv4 by just testing the rightmost label125// being a number.126int lastdot = hoststr.lastIndexOf('.');127if (lastdot != -1 && (hoststr.length() > 1)) {128boolean ipv4 = true;129130for (int i = lastdot + 1, len = hoststr.length(); i < len; i++) {131char c = hoststr.charAt(i);132if (c < '0' || c > '9') {133ipv4 = false;134break;135}136}137this.ipv4 = this.literal = ipv4;138if (ipv4) {139byte[] ip = IPAddressUtil.textToNumericFormatV4(hoststr);140if (ip == null) {141throw new IllegalArgumentException("illegal IPv4 address");142}143StringBuilder sb = new StringBuilder();144Formatter formatter = new Formatter(sb, Locale.US);145formatter.format("%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]);146hoststr = sb.toString();147} else {148// regular domain name149hoststr = toLowerCase(hoststr);150}151}152}153hostname = hoststr;154}155156try {157portrange = parsePort(portstr);158} catch (Exception e) {159throw new IllegalArgumentException("invalid port range: " + portstr);160}161}162163static final int CASE_DIFF = 'A' - 'a';164165/**166* Convert to lower case, and check that all chars are ascii167* alphanumeric, '-' or '.' only.168*/169static String toLowerCase(String s) {170int len = s.length();171StringBuilder sb = null;172173for (int i=0; i<len; i++) {174char c = s.charAt(i);175if ((c >= 'a' && c <= 'z') || (c == '.')) {176if (sb != null)177sb.append(c);178} else if ((c >= '0' && c <= '9') || (c == '-')) {179if (sb != null)180sb.append(c);181} else if (c >= 'A' && c <= 'Z') {182if (sb == null) {183sb = new StringBuilder(len);184sb.append(s, 0, i);185}186sb.append((char)(c - CASE_DIFF));187} else {188throw new IllegalArgumentException("Invalid characters in hostname");189}190}191return sb == null ? s : sb.toString();192}193194195public boolean literal() {196return literal;197}198199public boolean ipv4Literal() {200return ipv4;201}202203public boolean ipv6Literal() {204return ipv6;205}206207public String hostname() {208return hostname;209}210211public int[] portrange() {212return portrange;213}214215/**216* returns true if the hostname part started with *217* hostname returns the remaining part of the host component218* eg "*.foo.com" -> ".foo.com" or "*" -> ""219*220* @return221*/222public boolean wildcard() {223return wildcard;224}225226// these shouldn't leak outside the implementation227static final int[] HTTP_PORT = {80, 80};228static final int[] HTTPS_PORT = {443, 443};229static final int[] NO_PORT = {-1, -1};230231int[] defaultPort() {232if (scheme.equals("http")) {233return HTTP_PORT;234} else if (scheme.equals("https")) {235return HTTPS_PORT;236}237return NO_PORT;238}239240int[] parsePort(String port)241{242243if (port == null || port.isEmpty()) {244return defaultPort();245}246247if (port.equals("*")) {248return new int[] {PORT_MIN, PORT_MAX};249}250251try {252int dash = port.indexOf('-');253254if (dash == -1) {255int p = Integer.parseInt(port);256return new int[] {p, p};257} else {258String low = port.substring(0, dash);259String high = port.substring(dash+1);260int l,h;261262if (low.isEmpty()) {263l = PORT_MIN;264} else {265l = Integer.parseInt(low);266}267268if (high.isEmpty()) {269h = PORT_MAX;270} else {271h = Integer.parseInt(high);272}273if (l < 0 || h < 0 || h<l) {274return defaultPort();275}276return new int[] {l, h};277}278} catch (IllegalArgumentException e) {279return defaultPort();280}281}282}283284285