1 /*
2 * Copyright (C) 2008 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 *
16 *
17 * This file has been copied from http://code.google.com/p/guava-libraries/ and
18 * modified to remove unused methods as we cannot currently safely distribute
19 * Guava with the Platform common modules.
20 */
21
22 package com.atlassian.ip;
23
24 import java.net.Inet4Address;
25 import java.net.Inet6Address;
26 import java.net.InetAddress;
27 import java.net.UnknownHostException;
28 import java.nio.ByteBuffer;
29
30 /**
31 * Copied from http://code.google.com/p/guava-libraries/.
32 *
33 * Should be refactored away after https://studio.atlassian.com/browse/REFAPP-164
34 * has been resolved.
35 */
36 final class InetAddresses
37 {
38 private static final int IPV4_PART_COUNT = 4;
39 private static final int IPV6_PART_COUNT = 8;
40
41 private InetAddresses() {}
42
43 /**
44 * Returns the {@link java.net.InetAddress} having the given string
45 * representation.
46 *
47 * <p>This deliberately avoids all nameservice lookups (e.g. no DNS).
48 *
49 * @param ipString {@code String} containing an IPv4 or IPv6 string literal,
50 * e.g. {@code "192.168.0.1"} or {@code "2001:db8::1"}
51 * @return {@link java.net.InetAddress} representing the argument
52 * @throws IllegalArgumentException if the argument is not a valid
53 * IP string literal
54 */
55 public static InetAddress forString(String ipString) {
56 byte[] addr = textToNumericFormatV4(ipString);
57 if (addr == null) {
58 // Scanning for IPv4 string literal failed; try IPv6.
59 addr = textToNumericFormatV6(ipString);
60 }
61
62 // The argument was malformed, i.e. not an IP string literal.
63 if (addr == null) {
64 throw new IllegalArgumentException(
65 String.format("'%s' is not an IP string literal.", ipString));
66 }
67
68 try {
69 return InetAddress.getByAddress(addr);
70 } catch (UnknownHostException e) {
71
72 /*
73 * This really shouldn't happen in practice since all our byte
74 * sequences should be valid IP addresses.
75 *
76 * However {@link InetAddress#getByAddress} is documented as
77 * potentially throwing this "if IP address is of illegal length".
78 *
79 * This is mapped to IllegalArgumentException since, presumably,
80 * the argument triggered some processing bug in either
81 * {@link IPAddressUtil#textToNumericFormatV4} or
82 * {@link IPAddressUtil#textToNumericFormatV6}.
83 */
84 throw new IllegalArgumentException(
85 String.format("'%s' is extremely broken.", ipString), e);
86 }
87 }
88
89 /**
90 * Returns {@code true} if the supplied string is a valid IP string
91 * literal, {@code false} otherwise.
92 *
93 * @param ipString {@code String} to evaluated as an IP string literal
94 * @return {@code true} if the argument is a valid IP string literal
95 */
96 public static boolean isInetAddress(String ipString) {
97 try {
98 forString(ipString);
99 return true;
100 } catch (IllegalArgumentException e) {
101 return false;
102 }
103 }
104
105 private static byte[] textToNumericFormatV4(String ipString) {
106 if (ipString.contains(":")) {
107 // For the special mapped address cases (e.g. "::ffff:192.0.2.1") passing
108 // InetAddress.getByAddress() the output of textToNumericFormatV6()
109 // below will "do the right thing", i.e. construct an Inet4Address.
110 return null;
111 }
112
113 String[] address = ipString.split("\\.");
114 if (address.length != IPV4_PART_COUNT) {
115 return null;
116 }
117
118 byte[] bytes = new byte[IPV4_PART_COUNT];
119 try {
120 for (int i = 0; i < bytes.length; i++) {
121 int piece = Integer.parseInt(address[i]);
122 if (piece < 0 || piece > 255) {
123 return null;
124 }
125
126 // No leading zeroes are allowed. See
127 // http://tools.ietf.org/html/draft-main-ipaddr-text-rep-00
128 // section 2.1 for discussion.
129
130 if (address[i].startsWith("0") && address[i].length() != 1) {
131 return null;
132 }
133 bytes[i] = (byte) piece;
134 }
135 } catch (NumberFormatException ex) {
136 return null;
137 }
138
139 return bytes;
140 }
141
142 private static byte[] textToNumericFormatV6(String ipString) {
143 if (!ipString.contains(":")) {
144 return null;
145 }
146 if (ipString.contains(":::")) {
147 return null;
148 }
149
150 if (ipString.contains(".")) {
151 ipString = convertDottedQuadToHex(ipString);
152 if (ipString == null) {
153 return null;
154 }
155 }
156
157 ByteBuffer rawBytes = ByteBuffer.allocate(2 * IPV6_PART_COUNT);
158 // Keep a record of the number of parts specified above/before a "::"
159 // (partsHi), and below/after any "::" (partsLo).
160 int partsHi = 0;
161 int partsLo = 0;
162
163 String[] addressHalves = ipString.split("::", 2); // At most 1 "::".
164 // Parse parts above any "::", or the whole string if no "::" present.
165 if (!addressHalves[0].equals("")) {
166 String[] parts = addressHalves[0].split(":", IPV6_PART_COUNT);
167 try {
168 for (int i = 0; i < parts.length; i++) {
169 if (parts[i].equals("")) {
170 // No empty segments permitted.
171 return null;
172 }
173 int piece = Integer.parseInt(parts[i], 16);
174 rawBytes.putShort(2 * i, (short) piece);
175 }
176 partsHi = parts.length;
177 } catch (NumberFormatException ex) {
178 return null;
179 }
180 } else {
181 // A leading "::". At least one 16bit segment must be zero.
182 partsHi = 1;
183 }
184
185 // Parse parts below "::" (if any), into the tail end of the byte array,
186 // working backwards.
187 if (addressHalves.length > 1) {
188 if (!addressHalves[1].equals("")) {
189 String[] parts = addressHalves[1].split(":", IPV6_PART_COUNT);
190 try {
191 for (int i = 0; i < parts.length; i++) {
192 int partsIndex = parts.length - i - 1;
193 if (parts[partsIndex].equals("")) {
194 // No empty segments permitted.
195 return null;
196 }
197 int piece = Integer.parseInt(parts[partsIndex], 16);
198 int bytesIndex = 2 * (IPV6_PART_COUNT - i - 1);
199 rawBytes.putShort(bytesIndex, (short) piece);
200 }
201 partsLo = parts.length;
202 } catch (NumberFormatException ex) {
203 return null;
204 }
205 } else {
206 // A trailing "::". At least one 16bit segment must be zero.
207 partsLo = 1;
208 }
209 }
210
211 // Some extra sanity checks.
212 int totalParts = partsHi + partsLo;
213 if (totalParts > IPV6_PART_COUNT) {
214 return null;
215 }
216 if (addressHalves.length == 1 && totalParts != IPV6_PART_COUNT) {
217 // If no "::" shortening is used then all bytes must have been specified.
218 return null;
219 }
220
221 return rawBytes.array();
222 }
223
224 private static String convertDottedQuadToHex(String ipString) {
225 int lastColon = ipString.lastIndexOf(':');
226 String initialPart = ipString.substring(0, lastColon + 1);
227 String dottedQuad = ipString.substring(lastColon + 1);
228 byte[] quad = textToNumericFormatV4(dottedQuad);
229 if (quad == null) {
230 return null;
231 }
232 String penultimate = Integer.toHexString(((quad[0] & 0xff) << 8) | (quad[1] & 0xff));
233 String ultimate = Integer.toHexString(((quad[2] & 0xff) << 8) | (quad[3] & 0xff));
234 return initialPart + penultimate + ":" + ultimate;
235 }
236
237 /**
238 * Returns an InetAddress representing the literal IPv4 or IPv6 host
239 * portion of a URL, encoded in the format specified by RFC 3986 section 3.2.2.
240 *
241 * <p>This function is similar to {@link com.atlassian.ip.InetAddresses#forString(String)},
242 * however, it requires that IPv6 addresses are surrounded by square brackets.
243 *
244 * @param hostAddr A RFC 3986 section 3.2.2 encoded IPv4 or IPv6 address
245 * @return an InetAddress representing the address in {@code hostAddr}
246 * @throws IllegalArgumentException if {@code hostAddr} is not a valid
247 * IPv4 address, or IPv6 address surrounded by square brackets
248 */
249 public static InetAddress forUriString(String hostAddr) {
250 InetAddress retval = null;
251
252 // IPv4 address?
253 try {
254 retval = forString(hostAddr);
255 if (retval instanceof Inet4Address) {
256 return retval;
257 }
258 } catch (IllegalArgumentException e) {
259 // Not a valid IP address, fall through.
260 }
261
262 // IPv6 address
263 if (!(hostAddr.startsWith("[") && hostAddr.endsWith("]"))) {
264 throw new IllegalArgumentException("Not a valid address: \"" + hostAddr + '"');
265 }
266
267 retval = forString(hostAddr.substring(1, hostAddr.length() - 1));
268 if (retval instanceof Inet6Address) {
269 return retval;
270 }
271
272 throw new IllegalArgumentException("Not a valid address: \"" + hostAddr + '"');
273 }
274 }