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    }