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