summaryrefslogtreecommitdiffstats
path: root/guava/src/com/google/common/net/HostSpecifier.java
blob: 3c909859198795d9e5b437441214227022f48922 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
/*
 * Copyright (C) 2009 The Guava Authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.common.net;

import com.google.common.annotations.Beta;
import com.google.common.base.Preconditions;

import java.net.InetAddress;
import java.text.ParseException;

import javax.annotation.Nullable;

/**
 * A syntactically valid host specifier, suitable for use in a URI.
 * This may be either a numeric IP address in IPv4 or IPv6 notation, or a
 * domain name.
 *
 * <p>Because this class is intended to represent host specifiers which can
 * reasonably be used in a URI, the domain name case is further restricted to
 * include only those domain names which end in a recognized public suffix; see
 * {@link InternetDomainName#isPublicSuffix()} for details.
 *
 * <p>Note that no network lookups are performed by any {@code HostSpecifier}
 * methods.  No attempt is made to verify that a provided specifier corresponds
 * to a real or accessible host.  Only syntactic and pattern-based checks are
 * performed.
 *
 * <p>If you know that a given string represents a numeric IP address, use
 * {@link InetAddresses} to obtain and manipulate a
 * {@link java.net.InetAddress} instance from it rather than using this class.
 * Similarly, if you know that a given string represents a domain name, use
 * {@link InternetDomainName} rather than this class.
 *
 * @author Craig Berry
 * @since 5.0
 */
@Beta
public final class HostSpecifier {

  private final String canonicalForm;

  private HostSpecifier(String canonicalForm) {
    this.canonicalForm = canonicalForm;
  }

  /**
   * Returns a {@code HostSpecifier} built from the provided {@code specifier},
   * which is already known to be valid.  If the {@code specifier} might be
   * invalid, use {@link #from(String)} instead.
   *
   * <p>The specifier must be in one of these formats:
   * <ul>
   * <li>A domain name, like {@code google.com}
   * <li>A IPv4 address string, like {@code 127.0.0.1}
   * <li>An IPv6 address string with or without brackets, like
   *     {@code [2001:db8::1]} or {@code 2001:db8::1}
   * </ul>
   *
   * @throws IllegalArgumentException if the specifier is not valid.
   */
  public static HostSpecifier fromValid(String specifier) {
    // Verify that no port was specified, and strip optional brackets from
    // IPv6 literals.
    final HostAndPort parsedHost = HostAndPort.fromString(specifier);
    Preconditions.checkArgument(!parsedHost.hasPort());
    final String host = parsedHost.getHostText();

    // Try to interpret the specifier as an IP address.  Note we build
    // the address rather than using the .is* methods because we want to
    // use InetAddresses.toUriString to convert the result to a string in
    // canonical form.
    InetAddress addr = null;
    try {
      addr = InetAddresses.forString(host);
    } catch (IllegalArgumentException e) {
      // It is not an IPv4 or IPv6 literal
    }

    if (addr != null) {
      return new HostSpecifier(InetAddresses.toUriString(addr));
    }

    // It is not any kind of IP address; must be a domain name or invalid.

    // TODO(user): different versions of this for different factories?
    final InternetDomainName domain = InternetDomainName.from(host);

    if (domain.hasPublicSuffix()) {
      return new HostSpecifier(domain.name());
    }

    throw new IllegalArgumentException(
        "Domain name does not have a recognized public suffix: " + host);
  }

  /**
   * Attempts to return a {@code HostSpecifier} for the given string, throwing
   * an exception if parsing fails. Always use this method in preference to
   * {@link #fromValid(String)} for a specifier that is not already known to be
   * valid.
   *
   * @throws ParseException if the specifier is not valid.
   */
  public static HostSpecifier from(String specifier)
      throws ParseException {
    try {
      return fromValid(specifier);
    } catch (IllegalArgumentException e) {
      // Since the IAE can originate at several different points inside
      // fromValid(), we implement this method in terms of that one rather
      // than the reverse.

      ParseException parseException =
          new ParseException("Invalid host specifier: " + specifier, 0);
      parseException.initCause(e);
      throw parseException;
    }
  }

  /**
   * Determines whether {@code specifier} represents a valid
   * {@link HostSpecifier} as described in the documentation for
   * {@link #fromValid(String)}.
   */
  public static boolean isValid(String specifier) {
    try {
      fromValid(specifier);
      return true;
    } catch (IllegalArgumentException e) {
      return false;
    }
  }

  @Override
  public boolean equals(@Nullable Object other) {
    if (this == other) {
      return true;
    }

    if (other instanceof HostSpecifier) {
      final HostSpecifier that = (HostSpecifier) other;
      return this.canonicalForm.equals(that.canonicalForm);
    }

    return false;
  }

  @Override
  public int hashCode() {
    return canonicalForm.hashCode();
  }

  /**
   * Returns a string representation of the host specifier suitable for
   * inclusion in a URI.  If the host specifier is a domain name, the
   * string will be normalized to all lower case.  If the specifier was
   * an IPv6 address without brackets, brackets are added so that the
   * result will be usable in the host part of a URI.
   */
  @Override
  public String toString() {
    return canonicalForm;
  }
}