View Javadoc

1   /*
2    * Copyright 2012 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. 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, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package org.jboss.netty.util;
17  
18  import java.util.ArrayList;
19  import java.util.StringTokenizer;
20  
21  /**
22   * A class that holds a number of network-related constants.
23   * <p/>
24   * This class borrowed some of its methods from a  modified fork of the
25   * <a href="http://svn.apache.org/repos/asf/harmony/enhanced/java/branches/java6/classlib/modules/luni/
26   * src/main/java/org/apache/harmony/luni/util/Inet6Util.java">Inet6Util class</a> which was part of Apache Harmony.
27   */
28  public final class NetUtil {
29  
30      /**
31       * Creates an byte[] based on an ipAddressString. No error handling is
32       * performed here.
33       */
34      public static byte[] createByteArrayFromIpAddressString(
35              String ipAddressString) {
36  
37          if (isValidIpV4Address(ipAddressString)) {
38              StringTokenizer tokenizer = new StringTokenizer(ipAddressString,
39                      ".");
40              String token;
41              int tempInt;
42              byte[] byteAddress = new byte[4];
43              for (int i = 0; i < 4; i++) {
44                  token = tokenizer.nextToken();
45                  tempInt = Integer.parseInt(token);
46                  byteAddress[i] = (byte) tempInt;
47              }
48  
49              return byteAddress;
50          }
51  
52          if (isValidIpV6Address(ipAddressString)) {
53              if (ipAddressString.charAt(0) == '[') {
54                  ipAddressString = ipAddressString.substring(1, ipAddressString
55                          .length() - 1);
56              }
57  
58              StringTokenizer tokenizer = new StringTokenizer(ipAddressString, ":.",
59                      true);
60              ArrayList<String> hexStrings = new ArrayList<String>();
61              ArrayList<String> decStrings = new ArrayList<String>();
62              String token = "";
63              String prevToken = "";
64              int doubleColonIndex = -1; // If a double colon exists, we need to
65              // insert 0s.
66  
67              // Go through the tokens, including the seperators ':' and '.'
68              // When we hit a : or . the previous token will be added to either
69              // the hex list or decimal list. In the case where we hit a ::
70              // we will save the index of the hexStrings so we can add zeros
71              // in to fill out the string
72              while (tokenizer.hasMoreTokens()) {
73                  prevToken = token;
74                  token = tokenizer.nextToken();
75  
76                  if (":".equals(token)) {
77                      if (":".equals(prevToken)) {
78                          doubleColonIndex = hexStrings.size();
79                      } else if (prevToken.length() > 0) {
80                          hexStrings.add(prevToken);
81                      }
82                  } else if (".".equals(token)) {
83                      decStrings.add(prevToken);
84                  }
85              }
86  
87              if (":".equals(prevToken)) {
88                  if (":".equals(token)) {
89                      doubleColonIndex = hexStrings.size();
90                  } else {
91                      hexStrings.add(token);
92                  }
93              } else if (".".equals(prevToken)) {
94                  decStrings.add(token);
95              }
96  
97              // figure out how many hexStrings we should have
98              // also check if it is a IPv4 address
99              int hexStringsLength = 8;
100 
101             // If we have an IPv4 address tagged on at the end, subtract
102             // 4 bytes, or 2 hex words from the total
103             if (!decStrings.isEmpty()) {
104                 hexStringsLength -= 2;
105             }
106 
107             // if we hit a double Colon add the appropriate hex strings
108             if (doubleColonIndex != -1) {
109                 int numberToInsert = hexStringsLength - hexStrings.size();
110                 for (int i = 0; i < numberToInsert; i++) {
111                     hexStrings.add(doubleColonIndex, "0");
112                 }
113             }
114 
115             byte[] ipByteArray = new byte[16];
116 
117             // Finally convert these strings to bytes...
118             for (int i = 0; i < hexStrings.size(); i++) {
119                 convertToBytes(hexStrings.get(i), ipByteArray, i * 2);
120             }
121 
122             // Now if there are any decimal values, we know where they go...
123             for (int i = 0; i < decStrings.size(); i++) {
124                 ipByteArray[i + 12] = (byte) (Integer.parseInt(decStrings
125                         .get(i)) & 255);
126             }
127             return ipByteArray;
128         }
129         return null;
130     }
131 
132     /**
133      * Converts a 4 character hex word into a 2 byte word equivalent
134      */
135     private static void convertToBytes(String hexWord, byte[] ipByteArray,
136                                        int byteIndex) {
137 
138         int hexWordLength = hexWord.length();
139         int hexWordIndex = 0;
140         ipByteArray[byteIndex] = 0;
141         ipByteArray[byteIndex + 1] = 0;
142         int charValue;
143 
144         // high order 4 bits of first byte
145         if (hexWordLength > 3) {
146             charValue = getIntValue(hexWord.charAt(hexWordIndex++));
147             ipByteArray[byteIndex] |= charValue << 4;
148         }
149 
150         // low order 4 bits of the first byte
151         if (hexWordLength > 2) {
152             charValue = getIntValue(hexWord.charAt(hexWordIndex++));
153             ipByteArray[byteIndex] |= charValue;
154         }
155 
156         // high order 4 bits of second byte
157         if (hexWordLength > 1) {
158             charValue = getIntValue(hexWord.charAt(hexWordIndex++));
159             ipByteArray[byteIndex + 1] |= charValue << 4;
160         }
161 
162         // low order 4 bits of the first byte
163         charValue = getIntValue(hexWord.charAt(hexWordIndex));
164         ipByteArray[byteIndex + 1] |= charValue & 15;
165     }
166 
167     static int getIntValue(char c) {
168 
169         switch (c) {
170             case '0':
171                 return 0;
172             case '1':
173                 return 1;
174             case '2':
175                 return 2;
176             case '3':
177                 return 3;
178             case '4':
179                 return 4;
180             case '5':
181                 return 5;
182             case '6':
183                 return 6;
184             case '7':
185                 return 7;
186             case '8':
187                 return 8;
188             case '9':
189                 return 9;
190         }
191 
192         c = Character.toLowerCase(c);
193         switch (c) {
194             case 'a':
195                 return 10;
196             case 'b':
197                 return 11;
198             case 'c':
199                 return 12;
200             case 'd':
201                 return 13;
202             case 'e':
203                 return 14;
204             case 'f':
205                 return 15;
206         }
207         return 0;
208     }
209 
210     public static boolean isValidIpV6Address(String ipAddress) {
211         int length = ipAddress.length();
212         boolean doubleColon = false;
213         int numberOfColons = 0;
214         int numberOfPeriods = 0;
215         int numberOfPercent = 0;
216         StringBuilder word = new StringBuilder();
217         char c = 0;
218         char prevChar;
219         int offset = 0; // offset for [] ip addresses
220 
221         if (length < 2) {
222             return false;
223         }
224 
225         for (int i = 0; i < length; i++) {
226             prevChar = c;
227             c = ipAddress.charAt(i);
228             switch (c) {
229 
230                 // case for an open bracket [x:x:x:...x]
231                 case '[':
232                     if (i != 0) {
233                         return false; // must be first character
234                     }
235                     if (ipAddress.charAt(length - 1) != ']') {
236                         return false; // must have a close ]
237                     }
238                     offset = 1;
239                     if (length < 4) {
240                         return false;
241                     }
242                     break;
243 
244                 // case for a closed bracket at end of IP [x:x:x:...x]
245                 case ']':
246                     if (i != length - 1) {
247                         return false; // must be last charcter
248                     }
249                     if (ipAddress.charAt(0) != '[') {
250                         return false; // must have a open [
251                     }
252                     break;
253 
254                 // case for the last 32-bits represented as IPv4 x:x:x:x:x:x:d.d.d.d
255                 case '.':
256                     numberOfPeriods++;
257                     if (numberOfPeriods > 3) {
258                         return false;
259                     }
260                     if (!isValidIp4Word(word.toString())) {
261                         return false;
262                     }
263                     if (numberOfColons != 6 && !doubleColon) {
264                         return false;
265                     }
266                     // a special case ::1:2:3:4:5:d.d.d.d allows 7 colons with an
267                     // IPv4 ending, otherwise 7 :'s is bad
268                     if (numberOfColons == 7 && ipAddress.charAt(offset) != ':'
269                             && ipAddress.charAt(1 + offset) != ':') {
270                         return false;
271                     }
272                     word.delete(0, word.length());
273                     break;
274 
275                 case ':':
276                     // FIX "IP6 mechanism syntax #ip6-bad1"
277                     // An IPV6 address cannot start with a single ":".
278                     // Either it can starti with "::" or with a number.
279                     if (i == offset && (ipAddress.length() <= i || ipAddress.charAt(i + 1) != ':')) {
280                         return false;
281                     }
282                     // END FIX "IP6 mechanism syntax #ip6-bad1"
283                     numberOfColons++;
284                     if (numberOfColons > 7) {
285                         return false;
286                     }
287                     if (numberOfPeriods > 0) {
288                         return false;
289                     }
290                     if (prevChar == ':') {
291                         if (doubleColon) {
292                             return false;
293                         }
294                         doubleColon = true;
295                     }
296                     word.delete(0, word.length());
297                     break;
298                 case '%':
299                     if (numberOfColons == 0) {
300                         return false;
301                     }
302                     numberOfPercent++;
303 
304                     // validate that the stuff after the % is valid
305                     if (i + 1 >= length) {
306                         // in this case the percent is there but no number is
307                         // available
308                         return false;
309                     }
310                     try {
311                         Integer.parseInt(ipAddress.substring(i + 1));
312                     } catch (NumberFormatException e) {
313                         // right now we just support an integer after the % so if
314                         // this is not
315                         // what is there then return
316                         return false;
317                     }
318                     break;
319 
320                 default:
321                     if (numberOfPercent == 0) {
322                         if (word != null && word.length() > 3) {
323                             return false;
324                         }
325                         if (!isValidHexChar(c)) {
326                             return false;
327                         }
328                     }
329                     word.append(c);
330             }
331         }
332 
333         // Check if we have an IPv4 ending
334         if (numberOfPeriods > 0) {
335             // There is a test case with 7 colons and valid ipv4 this should resolve it
336             if (numberOfPeriods != 3 || !(isValidIp4Word(word.toString()) && numberOfColons < 7)) {
337                 return false;
338             }
339         } else {
340             // If we're at then end and we haven't had 7 colons then there is a
341             // problem unless we encountered a doubleColon
342             if (numberOfColons != 7 && !doubleColon) {
343                 return false;
344             }
345 
346             // If we have an empty word at the end, it means we ended in either
347             // a : or a .
348             // If we did not end in :: then this is invalid
349             if (numberOfPercent == 0) {
350                 if (word.length() == 0 && ipAddress.charAt(length - 1 - offset) == ':'
351                         && ipAddress.charAt(length - 2 - offset) != ':') {
352                     return false;
353                 }
354             }
355         }
356 
357         return true;
358     }
359 
360     public static boolean isValidIp4Word(String word) {
361         char c;
362         if (word.length() < 1 || word.length() > 3) {
363             return false;
364         }
365         for (int i = 0; i < word.length(); i++) {
366             c = word.charAt(i);
367             if (!(c >= '0' && c <= '9')) {
368                 return false;
369             }
370         }
371         if (Integer.parseInt(word) > 255) {
372             return false;
373         }
374         return true;
375     }
376 
377     static boolean isValidHexChar(char c) {
378         return c >= '0' && c <= '9' || c >= 'A' && c <= 'F'
379                 || c >= 'a' && c <= 'f';
380     }
381 
382     /**
383      * Takes a string and parses it to see if it is a valid IPV4 address.
384      *
385      * @return true, if the string represents an IPV4 address in dotted
386      *         notation, false otherwise
387      */
388     public static boolean isValidIpV4Address(String value) {
389 
390         int periods = 0;
391         int i;
392         int length = value.length();
393 
394         if (length > 15) {
395             return false;
396         }
397         char c;
398         StringBuilder word = new StringBuilder();
399         for (i = 0; i < length; i++) {
400             c = value.charAt(i);
401             if (c == '.') {
402                 periods++;
403                 if (periods > 3) {
404                     return false;
405                 }
406                 if (word.length() == 0) {
407                     return false;
408                 }
409                 if (Integer.parseInt(word.toString()) > 255) {
410                     return false;
411                 }
412                 word.delete(0, word.length());
413             } else if (!Character.isDigit(c)) {
414                 return false;
415             } else {
416                 if (word.length() > 2) {
417                     return false;
418                 }
419                 word.append(c);
420             }
421         }
422 
423         if (word.length() == 0 || Integer.parseInt(word.toString()) > 255) {
424             return false;
425         }
426         if (periods != 3) {
427             return false;
428         }
429         return true;
430     }
431 
432     /**
433      * A constructor to stop this class being constructed.
434      */
435     private NetUtil() {
436         // Unused
437     }
438 }