1 /* 2 * Copyright (C) 2009 The Guava Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.common.net; 18 19 import com.google.common.annotations.Beta; 20 import com.google.common.base.Preconditions; 21 22 import java.net.InetAddress; 23 import java.text.ParseException; 24 25 import javax.annotation.Nullable; 26 27 /** 28 * A syntactically valid host specifier, suitable for use in a URI. 29 * This may be either a numeric IP address in IPv4 or IPv6 notation, or a 30 * domain name. 31 * 32 * <p>Because this class is intended to represent host specifiers which can 33 * reasonably be used in a URI, the domain name case is further restricted to 34 * include only those domain names which end in a recognized public suffix; see 35 * {@link InternetDomainName#isPublicSuffix()} for details. 36 * 37 * <p>Note that no network lookups are performed by any {@code HostSpecifier} 38 * methods. No attempt is made to verify that a provided specifier corresponds 39 * to a real or accessible host. Only syntactic and pattern-based checks are 40 * performed. 41 * 42 * <p>If you know that a given string represents a numeric IP address, use 43 * {@link InetAddresses} to obtain and manipulate a 44 * {@link java.net.InetAddress} instance from it rather than using this class. 45 * Similarly, if you know that a given string represents a domain name, use 46 * {@link InternetDomainName} rather than this class. 47 * 48 * @author Craig Berry 49 * @since 5.0 50 */ 51 @Beta 52 public final class HostSpecifier { 53 54 private final String canonicalForm; 55 HostSpecifier(String canonicalForm)56 private HostSpecifier(String canonicalForm) { 57 this.canonicalForm = canonicalForm; 58 } 59 60 /** 61 * Returns a {@code HostSpecifier} built from the provided {@code specifier}, 62 * which is already known to be valid. If the {@code specifier} might be 63 * invalid, use {@link #from(String)} instead. 64 * 65 * <p>The specifier must be in one of these formats: 66 * <ul> 67 * <li>A domain name, like {@code google.com} 68 * <li>A IPv4 address string, like {@code 127.0.0.1} 69 * <li>An IPv6 address string with or without brackets, like 70 * {@code [2001:db8::1]} or {@code 2001:db8::1} 71 * </ul> 72 * 73 * @throws IllegalArgumentException if the specifier is not valid. 74 */ fromValid(String specifier)75 public static HostSpecifier fromValid(String specifier) { 76 // Verify that no port was specified, and strip optional brackets from 77 // IPv6 literals. 78 final HostAndPort parsedHost = HostAndPort.fromString(specifier); 79 Preconditions.checkArgument(!parsedHost.hasPort()); 80 final String host = parsedHost.getHostText(); 81 82 // Try to interpret the specifier as an IP address. Note we build 83 // the address rather than using the .is* methods because we want to 84 // use InetAddresses.toUriString to convert the result to a string in 85 // canonical form. 86 InetAddress addr = null; 87 try { 88 addr = InetAddresses.forString(host); 89 } catch (IllegalArgumentException e) { 90 // It is not an IPv4 or IPv6 literal 91 } 92 93 if (addr != null) { 94 return new HostSpecifier(InetAddresses.toUriString(addr)); 95 } 96 97 // It is not any kind of IP address; must be a domain name or invalid. 98 99 // TODO(user): different versions of this for different factories? 100 final InternetDomainName domain = InternetDomainName.from(host); 101 102 if (domain.hasPublicSuffix()) { 103 return new HostSpecifier(domain.toString()); 104 } 105 106 throw new IllegalArgumentException( 107 "Domain name does not have a recognized public suffix: " + host); 108 } 109 110 /** 111 * Attempts to return a {@code HostSpecifier} for the given string, throwing 112 * an exception if parsing fails. Always use this method in preference to 113 * {@link #fromValid(String)} for a specifier that is not already known to be 114 * valid. 115 * 116 * @throws ParseException if the specifier is not valid. 117 */ from(String specifier)118 public static HostSpecifier from(String specifier) 119 throws ParseException { 120 try { 121 return fromValid(specifier); 122 } catch (IllegalArgumentException e) { 123 // Since the IAE can originate at several different points inside 124 // fromValid(), we implement this method in terms of that one rather 125 // than the reverse. 126 127 ParseException parseException = 128 new ParseException("Invalid host specifier: " + specifier, 0); 129 parseException.initCause(e); 130 throw parseException; 131 } 132 } 133 134 /** 135 * Determines whether {@code specifier} represents a valid 136 * {@link HostSpecifier} as described in the documentation for 137 * {@link #fromValid(String)}. 138 */ isValid(String specifier)139 public static boolean isValid(String specifier) { 140 try { 141 fromValid(specifier); 142 return true; 143 } catch (IllegalArgumentException e) { 144 return false; 145 } 146 } 147 148 @Override equals(@ullable Object other)149 public boolean equals(@Nullable Object other) { 150 if (this == other) { 151 return true; 152 } 153 154 if (other instanceof HostSpecifier) { 155 final HostSpecifier that = (HostSpecifier) other; 156 return this.canonicalForm.equals(that.canonicalForm); 157 } 158 159 return false; 160 } 161 162 @Override hashCode()163 public int hashCode() { 164 return canonicalForm.hashCode(); 165 } 166 167 /** 168 * Returns a string representation of the host specifier suitable for 169 * inclusion in a URI. If the host specifier is a domain name, the 170 * string will be normalized to all lower case. If the specifier was 171 * an IPv6 address without brackets, brackets are added so that the 172 * result will be usable in the host part of a URI. 173 */ 174 @Override toString()175 public String toString() { 176 return canonicalForm; 177 } 178 } 179