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