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 }