    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    }