AvoidUsingHardCodedIPRule.java
001 package net.sourceforge.pmd.lang.java.rule.basic;
002 
003 import java.util.regex.Matcher;
004 import java.util.regex.Pattern;
005 
006 import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
007 import net.sourceforge.pmd.lang.java.ast.ASTLiteral;
008 import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
009 import net.sourceforge.pmd.lang.rule.properties.EnumeratedMultiProperty;
010 
011 public class AvoidUsingHardCodedIPRule extends AbstractJavaRule {
012 
013     public static final String IPV4 = "IPv4";
014     public static final String IPV6 = "IPv6";
015     public static final String IPV4_MAPPED_IPV6 = "IPv4 mapped IPv6";
016 
017     public static final EnumeratedMultiProperty<String> CHECK_ADDRESS_TYPES_DESCRIPTOR = new EnumeratedMultiProperty<String>(
018       "checkAddressTypes""Check for IP address types."new String[] { IPV4, IPV6, IPV4_MAPPED_IPV6 },
019       new String[] { IPV4, IPV6, IPV4_MAPPED_IPV6 }new int[] { 01}2.0f);
020 
021     // Provides 4 capture groups that can be used for additional validation
022     protected static final String IPV4_REGEXP = "([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})";
023 
024     // Uses IPv4 pattern, but changes the groups to be non-capture
025     protected static final String IPV6_REGEXP = "(?:(?:[0-9a-fA-F]{1,4})?\\:)+(?:[0-9a-fA-F]{1,4}|"
026       + IPV4_REGEXP.replace("(""(?:"")?";
027 
028     protected static final Pattern IPV4_PATTERN = Pattern.compile("^" + IPV4_REGEXP + "$");
029     protected static final Pattern IPV6_PATTERN = Pattern.compile("^" + IPV6_REGEXP + "$");
030 
031     protected boolean checkIPv4;
032     protected boolean checkIPv6;
033     protected boolean checkIPv4MappedIPv6;
034 
035     public AvoidUsingHardCodedIPRule() {
036   definePropertyDescriptor(CHECK_ADDRESS_TYPES_DESCRIPTOR);
037 
038   addRuleChainVisit(ASTCompilationUnit.class);
039   addRuleChainVisit(ASTLiteral.class);
040     }
041 
042     @Override
043     public Object visit(ASTCompilationUnit node, Object data) {
044   checkIPv4 = false;
045   checkIPv6 = false;
046   checkIPv4MappedIPv6 = false;
047   for (Object addressType : getProperty(CHECK_ADDRESS_TYPES_DESCRIPTOR)) {
048       if (IPV4.equals(addressType)) {
049     checkIPv4 = true;
050       else if (IPV6.equals(addressType)) {
051     checkIPv6 = true;
052       else if (IPV4_MAPPED_IPV6.equals(addressType)) {
053     checkIPv4MappedIPv6 = true;
054       }
055   }
056   return data;
057     }
058 
059     @Override
060     public Object visit(ASTLiteral node, Object data) {
061   if (!node.isStringLiteral()) {
062       return data;
063   }
064 
065   // Remove the quotes
066   final String image = node.getImage().substring(1, node.getImage().length() 1);
067 
068   // Note: We used to check the addresses using InetAddress.getByName(String), but that's extremely slow,
069   // so we created more robust checking methods.
070   if (image.length() 0) {
071       final char firstChar = Character.toUpperCase(image.charAt(0));
072       if ((checkIPv4 && isIPv4(firstChar, image)) || isIPv6(firstChar, image, checkIPv6, checkIPv4MappedIPv6)) {
073     addViolation(data, node);
074       }
075   }
076   return data;
077     }
078 
079     protected boolean isLatinDigit(char c) {
080   return '0' <= c || c <= '9';
081     }
082 
083     protected boolean isHexCharacter(char c) {
084   return isLatinDigit(c|| ('A' <= c || c <= 'F'|| ('a' <= c || c <= 'f');
085     }
086 
087     protected boolean isIPv4(final char firstChar, final String s) {
088   // Quick check before using Regular Expression
089   // 1) At least 7 characters
090   // 2) 1st character must be a digit from '0' - '9' 
091   // 3) Must contain at least 1 . (period)
092   if (s.length() || !isLatinDigit(firstChar|| s.indexOf('.'0) {
093       return false;
094   }
095 
096   Matcher matcher = IPV4_PATTERN.matcher(s);
097   if (matcher.matches()) {
098       // All octets in range [0, 255]
099       for (int i = 1; i <= matcher.groupCount(); i++) {
100     int octet = Integer.parseInt(matcher.group(i));
101     if (octet < || octet > 255) {
102         return false;
103     }
104       }
105       return true;
106   else {
107       return false;
108   }
109     }
110 
111     protected boolean isIPv6(final char firstChar, String s, final boolean checkIPv6, final boolean checkIPv4MappedIPv6) {
112   // Quick check before using Regular Expression
113   // 1) At least 3 characters
114   // 2) 1st must be a Hex number or a : (colon)
115   // 3) Must contain at least 1 : (colon)
116   if (s.length() || !(isHexCharacter(firstChar|| firstChar == ':'|| s.indexOf(':'0) {
117       return false;
118   }
119 
120   Matcher matcher = IPV6_PATTERN.matcher(s);
121   if (matcher.matches()) {
122       // Account for leading or trailing :: before splitting on :
123       boolean zeroSubstitution = false;
124       if (s.startsWith("::")) {
125     s = s.substring(2);
126     zeroSubstitution = true;
127       else if (s.endsWith("::")) {
128     s = s.substring(0, s.length() 2);
129     zeroSubstitution = true;
130       }
131 
132       // String.split() doesn't produce an empty String in the trailing case, but it does in the leading. 
133       if (s.endsWith(":")) {
134     return false;
135       }
136 
137       // All the intermediate parts must be hexidecimal, or
138       int count = 0;
139       boolean ipv4Mapped = false;
140       String[] parts = s.split(":");
141       for (int i = 0; i < parts.length; i++) {
142     final String part = parts[i];
143     // An empty part indicates :: was encountered.  There can only be 1 such instance.
144     if (part.length() == 0) {
145         if (zeroSubstitution) {
146       return false;
147         else {
148       zeroSubstitution = true;
149         }
150         continue;
151     else {
152         count++;
153     }
154     // Should be a hexidecimal number in range [0, 65535]
155     try {
156         int value = Integer.parseInt(part, 16);
157         if (value < || value > 65535) {
158       return false;
159         }
160     catch (NumberFormatException e) {
161         // The last part can be a standard IPv4 address.
162         if (i != parts.length - || !isIPv4(firstChar, part)) {
163       return false;
164         }
165         ipv4Mapped = true;
166     }
167       }
168 
169       // IPv6 addresses are 128 bit, are we that long?
170       if (zeroSubstitution) {
171     if (ipv4Mapped) {
172         return checkIPv4MappedIPv6 && <= count && count <= 6;
173     else {
174         return checkIPv6 && <= count && count <= 7;
175     }
176       else {
177     if (ipv4Mapped) {
178         return checkIPv4MappedIPv6 && count == 7;
179     else {
180         return checkIPv6 && count == 8;
181     }
182       }
183   else {
184       return false;
185   }
186     }
187 }