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    }