001/* 002 * Copyright (C) 2009 The Guava Authors 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 005 * in compliance with the License. You may obtain a copy of the License at 006 * 007 * http://www.apache.org/licenses/LICENSE-2.0 008 * 009 * Unless required by applicable law or agreed to in writing, software distributed under the License 010 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 011 * or implied. See the License for the specific language governing permissions and limitations under 012 * the License. 013 */ 014 015package com.google.common.net; 016 017import com.google.common.annotations.GwtIncompatible; 018import com.google.common.annotations.J2ktIncompatible; 019import com.google.common.base.Preconditions; 020import com.google.errorprone.annotations.CanIgnoreReturnValue; 021import java.net.InetAddress; 022import java.text.ParseException; 023import org.jspecify.annotations.Nullable; 024 025/** 026 * A syntactically valid host specifier, suitable for use in a URI. This may be either a numeric IP 027 * address in IPv4 or IPv6 notation, or a domain name. 028 * 029 * <p>Because this class is intended to represent host specifiers which can reasonably be used in a 030 * URI, the domain name case is further restricted to include only those domain names which end in a 031 * recognized public suffix; see {@link InternetDomainName#isPublicSuffix()} for details. 032 * 033 * <p>In this context, "URI" is used in the general sense, and {@code HostSpecifier} does not 034 * guarantee that every URI or URL parser will accept the original input without additional 035 * normalization. {@code HostSpecifier} follows the syntactic rules of {@link InetAddresses} and 036 * {@link InternetDomainName}, including their documented acceptance of some inputs that particular 037 * URI implementations may reject, such as non-ASCII digits, non-ASCII domain names, or domain 038 * labels containing underscores. 039 * 040 * <p>Note that no network lookups are performed by any {@code HostSpecifier} methods. No attempt is 041 * made to verify that a provided specifier corresponds to a real or accessible host. Only syntactic 042 * and pattern-based checks are performed. 043 * 044 * <p>If you know that a given string represents a numeric IP address, use {@link InetAddresses} to 045 * obtain and manipulate a {@link java.net.InetAddress} instance from it rather than using this 046 * class. Similarly, if you know that a given string represents a domain name, use {@link 047 * InternetDomainName} rather than this class. 048 * 049 * @author Craig Berry 050 * @since 5.0 051 */ 052@J2ktIncompatible 053@GwtIncompatible 054public final class HostSpecifier { 055 056 private final String canonicalForm; 057 058 private HostSpecifier(String canonicalForm) { 059 this.canonicalForm = canonicalForm; 060 } 061 062 /** 063 * Returns a {@code HostSpecifier} built from the provided {@code specifier}, which is already 064 * known to be valid. If the {@code specifier} might be invalid, use {@link #from(String)} 065 * instead. 066 * 067 * <p>The specifier must be in one of these formats: 068 * 069 * <ul> 070 * <li>A domain name, like {@code google.com} 071 * <li>A IPv4 address string, like {@code 127.0.0.1} 072 * <li>An IPv6 address string with or without brackets, like {@code [2001:db8::1]} or {@code 073 * 2001:db8::1} 074 * </ul> 075 * 076 * @throws IllegalArgumentException if the specifier is not valid. 077 */ 078 public static HostSpecifier fromValid(String specifier) { 079 // Verify that no port was specified, and strip optional brackets from 080 // IPv6 literals. 081 HostAndPort parsedHost = HostAndPort.fromString(specifier); 082 Preconditions.checkArgument(!parsedHost.hasPort()); 083 String host = parsedHost.getHost(); 084 085 // Try to interpret the specifier as an IP address. Note we build 086 // the address rather than using the .is* methods because we want to 087 // use InetAddresses.toUriString to convert the result to a string in 088 // canonical form. 089 InetAddress addr = null; 090 try { 091 addr = InetAddresses.forString(host); 092 } catch (IllegalArgumentException e) { 093 // It is not an IPv4 or IPv6 literal 094 } 095 096 if (addr != null) { 097 return new HostSpecifier(InetAddresses.toUriString(addr)); 098 } 099 100 // It is not any kind of IP address; must be a domain name or invalid. 101 102 // TODO(user): different versions of this for different factories? 103 InternetDomainName domain = InternetDomainName.from(host); 104 105 if (domain.hasPublicSuffix()) { 106 return new HostSpecifier(domain.toString()); 107 } 108 109 throw new IllegalArgumentException( 110 "Domain name does not have a recognized public suffix: " + host); 111 } 112 113 /** 114 * Attempts to return a {@code HostSpecifier} for the given string, throwing an exception if 115 * parsing fails. Always use this method in preference to {@link #fromValid(String)} for a 116 * specifier that is not already known to be valid. 117 * 118 * @throws ParseException if the specifier is not valid. 119 */ 120 @CanIgnoreReturnValue // TODO(b/219820829): consider removing 121 public static HostSpecifier from(String specifier) throws ParseException { 122 try { 123 return fromValid(specifier); 124 } catch (IllegalArgumentException e) { 125 // Since the IAE can originate at several different points inside 126 // fromValid(), we implement this method in terms of that one rather 127 // than the reverse. 128 129 ParseException parseException = new ParseException("Invalid host specifier: " + specifier, 0); 130 parseException.initCause(e); 131 throw parseException; 132 } 133 } 134 135 /** 136 * Determines whether {@code specifier} represents a valid {@link HostSpecifier} as described in 137 * the documentation for {@link #fromValid(String)}. 138 */ 139 public static boolean isValid(String specifier) { 140 try { 141 HostSpecifier unused = fromValid(specifier); 142 return true; 143 } catch (IllegalArgumentException e) { 144 return false; 145 } 146 } 147 148 @Override 149 public boolean equals(@Nullable Object other) { 150 if (this == other) { 151 return true; 152 } 153 154 if (other instanceof HostSpecifier) { 155 HostSpecifier that = (HostSpecifier) other; 156 return this.canonicalForm.equals(that.canonicalForm); 157 } 158 159 return false; 160 } 161 162 @Override 163 public int hashCode() { 164 return canonicalForm.hashCode(); 165 } 166 167 /** 168 * Returns a string representation of the host specifier suitable for inclusion in a URI. If the 169 * host specifier is a domain name, the string will be normalized to all lower case. If the 170 * specifier was an IPv6 address without brackets, brackets are added so that the result will be 171 * usable in the host part of a URI. 172 */ 173 @Override 174 public String toString() { 175 return canonicalForm; 176 } 177}