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 }