1/*
2 * Copyright (C) 2009 The Guava Authors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.google.common.net;
18
19import com.google.common.annotations.Beta;
20import com.google.common.base.Preconditions;
21
22import java.net.InetAddress;
23import java.text.ParseException;
24
25import javax.annotation.Nullable;
26
27/**
28 * A syntactically valid host specifier, suitable for use in a URI.
29 * This may be either a numeric IP address in IPv4 or IPv6 notation, or a
30 * domain name.
31 *
32 * <p>Because this class is intended to represent host specifiers which can
33 * reasonably be used in a URI, the domain name case is further restricted to
34 * include only those domain names which end in a recognized public suffix; see
35 * {@link InternetDomainName#isPublicSuffix()} for details.
36 *
37 * <p>Note that no network lookups are performed by any {@code HostSpecifier}
38 * methods.  No attempt is made to verify that a provided specifier corresponds
39 * to a real or accessible host.  Only syntactic and pattern-based checks are
40 * performed.
41 *
42 * <p>If you know that a given string represents a numeric IP address, use
43 * {@link InetAddresses} to obtain and manipulate a
44 * {@link java.net.InetAddress} instance from it rather than using this class.
45 * Similarly, if you know that a given string represents a domain name, use
46 * {@link InternetDomainName} rather than this class.
47 *
48 * @author Craig Berry
49 * @since 5.0
50 */
51@Beta
52public final class HostSpecifier {
53
54  private final String canonicalForm;
55
56  private HostSpecifier(String canonicalForm) {
57    this.canonicalForm = canonicalForm;
58  }
59
60  /**
61   * Returns a {@code HostSpecifier} built from the provided {@code specifier},
62   * which is already known to be valid.  If the {@code specifier} might be
63   * invalid, use {@link #from(String)} instead.
64   *
65   * <p>The specifier must be in one of these formats:
66   * <ul>
67   * <li>A domain name, like {@code google.com}
68   * <li>A IPv4 address string, like {@code 127.0.0.1}
69   * <li>An IPv6 address string with or without brackets, like
70   *     {@code [2001:db8::1]} or {@code 2001:db8::1}
71   * </ul>
72   *
73   * @throws IllegalArgumentException if the specifier is not valid.
74   */
75  public static HostSpecifier fromValid(String specifier) {
76    // Verify that no port was specified, and strip optional brackets from
77    // IPv6 literals.
78    final HostAndPort parsedHost = HostAndPort.fromString(specifier);
79    Preconditions.checkArgument(!parsedHost.hasPort());
80    final String host = parsedHost.getHostText();
81
82    // Try to interpret the specifier as an IP address.  Note we build
83    // the address rather than using the .is* methods because we want to
84    // use InetAddresses.toUriString to convert the result to a string in
85    // canonical form.
86    InetAddress addr = null;
87    try {
88      addr = InetAddresses.forString(host);
89    } catch (IllegalArgumentException e) {
90      // It is not an IPv4 or IPv6 literal
91    }
92
93    if (addr != null) {
94      return new HostSpecifier(InetAddresses.toUriString(addr));
95    }
96
97    // It is not any kind of IP address; must be a domain name or invalid.
98
99    // TODO(user): different versions of this for different factories?
100    final InternetDomainName domain = InternetDomainName.from(host);
101
102    if (domain.hasPublicSuffix()) {
103      return new HostSpecifier(domain.toString());
104    }
105
106    throw new IllegalArgumentException(
107        "Domain name does not have a recognized public suffix: " + host);
108  }
109
110  /**
111   * Attempts to return a {@code HostSpecifier} for the given string, throwing
112   * an exception if parsing fails. Always use this method in preference to
113   * {@link #fromValid(String)} for a specifier that is not already known to be
114   * valid.
115   *
116   * @throws ParseException if the specifier is not valid.
117   */
118  public static HostSpecifier from(String specifier)
119      throws ParseException {
120    try {
121      return fromValid(specifier);
122    } catch (IllegalArgumentException e) {
123      // Since the IAE can originate at several different points inside
124      // fromValid(), we implement this method in terms of that one rather
125      // than the reverse.
126
127      ParseException parseException =
128          new ParseException("Invalid host specifier: " + specifier, 0);
129      parseException.initCause(e);
130      throw parseException;
131    }
132  }
133
134  /**
135   * Determines whether {@code specifier} represents a valid
136   * {@link HostSpecifier} as described in the documentation for
137   * {@link #fromValid(String)}.
138   */
139  public static boolean isValid(String specifier) {
140    try {
141      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      final 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
169   * inclusion in a URI.  If the host specifier is a domain name, the
170   * string will be normalized to all lower case.  If the specifier was
171   * an IPv6 address without brackets, brackets are added so that the
172   * result will be usable in the host part of a URI.
173   */
174  @Override
175  public String toString() {
176    return canonicalForm;
177  }
178}
179