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