View Javadoc

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 }