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
021 import java.net.InetAddress;
022 import java.text.ParseException;
023
024 import javax.annotation.Nullable;
025
026 /**
027 * A syntactically valid host specifier, suitable for use in a URI.
028 * This may be either a numeric IP address in IPv4 or IPv6 notation, or a
029 * domain name.
030 *
031 * <p>Because this class is intended to represent host specifiers which can
032 * reasonably be used in a URI, the domain name case is further restricted to
033 * include only those domain names which end in a recognized public suffix; see
034 * {@link InternetDomainName#isPublicSuffix()} for details.
035 *
036 * <p>Note that no network lookups are performed by any {@code HostSpecifier}
037 * methods. No attempt is made to verify that a provided specifier corresponds
038 * to a real or accessible host. Only syntactic and pattern-based checks are
039 * performed.
040 *
041 * <p>If you know that a given string represents a numeric IP address, use
042 * {@link InetAddresses} to obtain and manipulate a
043 * {@link java.net.InetAddress} instance from it rather than using this class.
044 * Similarly, if you know that a given string represents a domain name, use
045 * {@link InternetDomainName} rather than this class.
046 *
047 * @author Craig Berry
048 * @since 5
049 */
050 @Beta
051 public final class HostSpecifier {
052
053 private final String canonicalForm;
054
055 private HostSpecifier(String canonicalForm) {
056 this.canonicalForm = canonicalForm;
057 }
058
059 /**
060 * Returns a {@code HostSpecifier} built from the provided {@code specifier},
061 * which is already known to be valid. If the {@code specifier} might be
062 * invalid, use {@link #from(String)} instead.
063 *
064 * <p>The specifier must be in one of these formats:
065 * <ul>
066 * <li>A domain name, like {@code google.com}
067 * <li>A IPv4 address string, like {@code 127.0.0.1}
068 * <li>An IPv6 address string with or without brackets, like
069 * {@code [2001:db8::1]} or {@code 2001:db8::1}
070 * <li>An IPv6 address string enclosed in square brackets, like
071 * {[2001:db8::1]}
072 * </ul>
073 *
074 * @throws IllegalArgumentException if the specifier is not valid.
075 */
076 public static HostSpecifier fromValid(String specifier) {
077 // First, try to interpret the specifier as an IP address. Note we build
078 // the address rather than using the .is* methods because we want to
079 // use InetAddresses.toUriString to convert the result to a string in
080 // canonical form.
081
082 InetAddress addr = null;
083
084 try {
085 addr = InetAddresses.forString(specifier);
086 } catch (IllegalArgumentException e) {
087 // It is not an IPv4 or bracketless IPv6 specifier
088 }
089
090 if (addr == null) {
091 try {
092 addr = InetAddresses.forUriString(specifier);
093 } catch (IllegalArgumentException e) {
094 // It is not a bracketed IPv6 specifier
095 }
096 }
097
098 if (addr != null) {
099 return new HostSpecifier(InetAddresses.toUriString(addr));
100 }
101
102 // It is not any kind of IP address; must be a domain name or invalid.
103
104 // TODO(user): different lenient and strict versions of this?
105 final InternetDomainName domain = InternetDomainName.fromLenient(specifier);
106
107 if (domain.hasPublicSuffix()) {
108 return new HostSpecifier(domain.name());
109 }
110
111 throw new IllegalArgumentException(
112 "Domain name does not have a recognized public suffix: " + specifier);
113 }
114
115 /**
116 * Attempts to return a {@code HostSpecifier} for the given string, throwing
117 * an exception if parsing fails. Always use this method in preference to
118 * {@link #fromValid(String)} for a specifier that is not already known to be
119 * valid.
120 *
121 * @throws ParseException if the specifier is not valid.
122 */
123 public static HostSpecifier from(String specifier)
124 throws ParseException {
125 try {
126 return fromValid(specifier);
127 } catch (IllegalArgumentException e) {
128 // Since the IAE can originate at several different points inside
129 // fromValid(), we implement this method in terms of that one rather
130 // than the reverse.
131
132 ParseException parseException =
133 new ParseException("Invalid host specifier: " + specifier, 0);
134 parseException.initCause(e);
135 throw parseException;
136 }
137 }
138
139 /**
140 * Determines whether {@code specifier} represents a valid
141 * {@link HostSpecifier} as described in the documentation for
142 * {@link #fromValid(String)}.
143 */
144 public static boolean isValid(String specifier) {
145 try {
146 fromValid(specifier);
147 return true;
148 } catch (IllegalArgumentException e) {
149 return false;
150 }
151 }
152
153 @Override
154 public boolean equals(@Nullable Object other) {
155 if (this == other) {
156 return true;
157 }
158
159 if (other instanceof HostSpecifier) {
160 final HostSpecifier that = (HostSpecifier) other;
161 return this.canonicalForm.equals(that.canonicalForm);
162 }
163
164 return false;
165 }
166
167 @Override
168 public int hashCode() {
169 return canonicalForm.hashCode();
170 }
171
172 /**
173 * Returns a string representation of the host specifier suitable for
174 * inclusion in a URI. If the host specifier is a domain name, the
175 * string will be normalized to all lower case. If the specifier was
176 * an IPv6 address without brackets, brackets are added so that the
177 * result will be usable in the host part of a URI.
178 */
179 @Override
180 public String toString() {
181 return canonicalForm;
182 }
183 }