• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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