001/* 002 * Copyright (C) 2009 The Guava Authors 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 005 * in compliance with the License. You may obtain a copy of the License at 006 * 007 * http://www.apache.org/licenses/LICENSE-2.0 008 * 009 * Unless required by applicable law or agreed to in writing, software distributed under the License 010 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 011 * or implied. See the License for the specific language governing permissions and limitations under 012 * the License. 013 */ 014 015package com.google.common.net; 016 017import com.google.common.annotations.Beta; 018import com.google.common.annotations.GwtIncompatible; 019import com.google.common.base.Preconditions; 020import java.net.InetAddress; 021import java.text.ParseException; 022import org.checkerframework.checker.nullness.qual.Nullable; 023 024/** 025 * A syntactically valid host specifier, suitable for use in a URI. This may be either a numeric IP 026 * address in IPv4 or IPv6 notation, or a domain name. 027 * 028 * <p>Because this class is intended to represent host specifiers which can reasonably be used in a 029 * URI, the domain name case is further restricted to include only those domain names which end in a 030 * recognized public suffix; see {@link InternetDomainName#isPublicSuffix()} for details. 031 * 032 * <p>Note that no network lookups are performed by any {@code HostSpecifier} methods. No attempt is 033 * made to verify that a provided specifier corresponds to a real or accessible host. Only syntactic 034 * and pattern-based checks are performed. 035 * 036 * <p>If you know that a given string represents a numeric IP address, use {@link InetAddresses} to 037 * obtain and manipulate a {@link java.net.InetAddress} instance from it rather than using this 038 * class. Similarly, if you know that a given string represents a domain name, use {@link 039 * InternetDomainName} rather than this class. 040 * 041 * @author Craig Berry 042 * @since 5.0 043 */ 044@Beta 045@GwtIncompatible 046public final class HostSpecifier { 047 048 private final String canonicalForm; 049 050 private HostSpecifier(String canonicalForm) { 051 this.canonicalForm = canonicalForm; 052 } 053 054 /** 055 * Returns a {@code HostSpecifier} built from the provided {@code specifier}, which is already 056 * known to be valid. If the {@code specifier} might be invalid, use {@link #from(String)} 057 * instead. 058 * 059 * <p>The specifier must be in one of these formats: 060 * 061 * <ul> 062 * <li>A domain name, like {@code google.com} 063 * <li>A IPv4 address string, like {@code 127.0.0.1} 064 * <li>An IPv6 address string with or without brackets, like {@code [2001:db8::1]} or {@code 065 * 2001:db8::1} 066 * </ul> 067 * 068 * @throws IllegalArgumentException if the specifier is not valid. 069 */ 070 public static HostSpecifier fromValid(String specifier) { 071 // Verify that no port was specified, and strip optional brackets from 072 // IPv6 literals. 073 final HostAndPort parsedHost = HostAndPort.fromString(specifier); 074 Preconditions.checkArgument(!parsedHost.hasPort()); 075 final String host = parsedHost.getHost(); 076 077 // 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 InetAddress addr = null; 082 try { 083 addr = InetAddresses.forString(host); 084 } catch (IllegalArgumentException e) { 085 // It is not an IPv4 or IPv6 literal 086 } 087 088 if (addr != null) { 089 return new HostSpecifier(InetAddresses.toUriString(addr)); 090 } 091 092 // It is not any kind of IP address; must be a domain name or invalid. 093 094 // TODO(user): different versions of this for different factories? 095 final InternetDomainName domain = InternetDomainName.from(host); 096 097 if (domain.hasPublicSuffix()) { 098 return new HostSpecifier(domain.toString()); 099 } 100 101 throw new IllegalArgumentException( 102 "Domain name does not have a recognized public suffix: " + host); 103 } 104 105 /** 106 * Attempts to return a {@code HostSpecifier} for the given string, throwing an exception if 107 * parsing fails. Always use this method in preference to {@link #fromValid(String)} for a 108 * specifier that is not already known to be valid. 109 * 110 * @throws ParseException if the specifier is not valid. 111 */ 112 public static HostSpecifier from(String specifier) throws ParseException { 113 try { 114 return fromValid(specifier); 115 } catch (IllegalArgumentException e) { 116 // Since the IAE can originate at several different points inside 117 // fromValid(), we implement this method in terms of that one rather 118 // than the reverse. 119 120 ParseException parseException = new ParseException("Invalid host specifier: " + specifier, 0); 121 parseException.initCause(e); 122 throw parseException; 123 } 124 } 125 126 /** 127 * Determines whether {@code specifier} represents a valid {@link HostSpecifier} as described in 128 * the documentation for {@link #fromValid(String)}. 129 */ 130 public static boolean isValid(String specifier) { 131 try { 132 fromValid(specifier); 133 return true; 134 } catch (IllegalArgumentException e) { 135 return false; 136 } 137 } 138 139 @Override 140 public boolean equals(@Nullable Object other) { 141 if (this == other) { 142 return true; 143 } 144 145 if (other instanceof HostSpecifier) { 146 final HostSpecifier that = (HostSpecifier) other; 147 return this.canonicalForm.equals(that.canonicalForm); 148 } 149 150 return false; 151 } 152 153 @Override 154 public int hashCode() { 155 return canonicalForm.hashCode(); 156 } 157 158 /** 159 * Returns a string representation of the host specifier suitable for inclusion in a URI. If the 160 * host specifier is a domain name, the string will be normalized to all lower case. If the 161 * specifier was an IPv6 address without brackets, brackets are added so that the result will be 162 * usable in the host part of a URI. 163 */ 164 @Override 165 public String toString() { 166 return canonicalForm; 167 } 168}