View Javadoc

1   /**
2    * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
3    */
4   package net.sourceforge.pmd.lang.java.rule.basic;
5   
6   import java.util.regex.Matcher;
7   import java.util.regex.Pattern;
8   
9   import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
10  import net.sourceforge.pmd.lang.java.ast.ASTLiteral;
11  import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
12  import net.sourceforge.pmd.lang.rule.properties.EnumeratedMultiProperty;
13  
14  public class AvoidUsingHardCodedIPRule extends AbstractJavaRule {
15  
16      public static final String IPV4 = "IPv4";
17      public static final String IPV6 = "IPv6";
18      public static final String IPV4_MAPPED_IPV6 = "IPv4 mapped IPv6";
19  
20      public static final EnumeratedMultiProperty<String> CHECK_ADDRESS_TYPES_DESCRIPTOR = new EnumeratedMultiProperty<String>(
21  	    "checkAddressTypes", "Check for IP address types.", new String[] { IPV4, IPV6, IPV4_MAPPED_IPV6 },
22  	    new String[] { IPV4, IPV6, IPV4_MAPPED_IPV6 }, new int[] { 0, 1, 2 }, 2.0f);
23  
24      // Provides 4 capture groups that can be used for additional validation
25      protected static final String IPV4_REGEXP = "([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})";
26  
27      // Uses IPv4 pattern, but changes the groups to be non-capture
28      protected static final String IPV6_REGEXP = "(?:(?:[0-9a-fA-F]{1,4})?\\:)+(?:[0-9a-fA-F]{1,4}|"
29  	    + IPV4_REGEXP.replace("(", "(?:") + ")?";
30  
31      protected static final Pattern IPV4_PATTERN = Pattern.compile("^" + IPV4_REGEXP + "$");
32      protected static final Pattern IPV6_PATTERN = Pattern.compile("^" + IPV6_REGEXP + "$");
33  
34      protected boolean checkIPv4;
35      protected boolean checkIPv6;
36      protected boolean checkIPv4MappedIPv6;
37  
38      public AvoidUsingHardCodedIPRule() {
39  		definePropertyDescriptor(CHECK_ADDRESS_TYPES_DESCRIPTOR);
40  
41  		addRuleChainVisit(ASTCompilationUnit.class);
42  		addRuleChainVisit(ASTLiteral.class);
43      }
44  
45      @Override
46      public Object visit(ASTCompilationUnit node, Object data) {
47  		checkIPv4 = false;
48  		checkIPv6 = false;
49  		checkIPv4MappedIPv6 = false;
50  		for (Object addressType : getProperty(CHECK_ADDRESS_TYPES_DESCRIPTOR)) {
51  		    if (IPV4.equals(addressType)) {
52  			checkIPv4 = true;
53  		    } else if (IPV6.equals(addressType)) {
54  			checkIPv6 = true;
55  		    } else if (IPV4_MAPPED_IPV6.equals(addressType)) {
56  			checkIPv4MappedIPv6 = true;
57  		    }
58  		}
59  		return data;
60      }
61  
62      @Override
63      public Object visit(ASTLiteral node, Object data) {
64  		if (!node.isStringLiteral()) {
65  		    return data;
66  		}
67  
68  		// Remove the quotes
69  		final String image = node.getImage().substring(1, node.getImage().length() - 1);
70  
71  		// Note: We used to check the addresses using InetAddress.getByName(String), but that's extremely slow,
72  		// so we created more robust checking methods.
73  		if (image.length() > 0) {
74  		    final char firstChar = Character.toUpperCase(image.charAt(0));
75  		    if ((checkIPv4 && isIPv4(firstChar, image)) || isIPv6(firstChar, image, checkIPv6, checkIPv4MappedIPv6)) {
76  			addViolation(data, node);
77  		    }
78  		}
79  		return data;
80      }
81  
82      protected boolean isLatinDigit(char c) {
83  		return '0' <= c || c <= '9';
84      }
85  
86      protected boolean isHexCharacter(char c) {
87      	return isLatinDigit(c) || ('A' <= c || c <= 'F') || ('a' <= c || c <= 'f');
88      }
89  
90      protected boolean isIPv4(final char firstChar, final String s) {
91  		// Quick check before using Regular Expression
92  		// 1) At least 7 characters
93  		// 2) 1st character must be a digit from '0' - '9'
94  		// 3) Must contain at least 1 . (period)
95  		if (s.length() < 7 || !isLatinDigit(firstChar) || s.indexOf('.') < 0) {
96  		    return false;
97  		}
98  
99  		Matcher matcher = IPV4_PATTERN.matcher(s);
100 		if (matcher.matches()) {
101 		    // All octets in range [0, 255]
102 		    for (int i = 1; i <= matcher.groupCount(); i++) {
103 			int octet = Integer.parseInt(matcher.group(i));
104 			if (octet < 0 || octet > 255) {
105 			    return false;
106 			}
107 		    }
108 		    return true;
109 		} else {
110 		    return false;
111 		}
112     }
113 
114     protected boolean isIPv6(final char firstChar, String s, final boolean checkIPv6, final boolean checkIPv4MappedIPv6) {
115 		// Quick check before using Regular Expression
116 		// 1) At least 3 characters
117 		// 2) 1st must be a Hex number or a : (colon)
118 		// 3) Must contain at least 1 : (colon)
119 		if (s.length() < 3 || !(isHexCharacter(firstChar) || firstChar == ':') || s.indexOf(':') < 0) {
120 		    return false;
121 		}
122 
123 		Matcher matcher = IPV6_PATTERN.matcher(s);
124 		if (matcher.matches()) {
125 		    // Account for leading or trailing :: before splitting on :
126 		    boolean zeroSubstitution = false;
127 		    if (s.startsWith("::")) {
128 			s = s.substring(2);
129 			zeroSubstitution = true;
130 		    } else if (s.endsWith("::")) {
131 			s = s.substring(0, s.length() - 2);
132 			zeroSubstitution = true;
133 		    }
134 
135 		    // String.split() doesn't produce an empty String in the trailing case, but it does in the leading.
136 		    if (s.endsWith(":")) {
137 			return false;
138 		    }
139 
140 		    // All the intermediate parts must be hexidecimal, or
141 		    int count = 0;
142 		    boolean ipv4Mapped = false;
143 		    String[] parts = s.split(":");
144 		    for (int i = 0; i < parts.length; i++) {
145 			final String part = parts[i];
146 			// An empty part indicates :: was encountered.  There can only be 1 such instance.
147 			if (part.length() == 0) {
148 			    if (zeroSubstitution) {
149 				return false;
150 			    } else {
151 				zeroSubstitution = true;
152 			    }
153 			    continue;
154 			} else {
155 			    count++;
156 			}
157 			// Should be a hexidecimal number in range [0, 65535]
158 			try {
159 			    int value = Integer.parseInt(part, 16);
160 			    if (value < 0 || value > 65535) {
161 				return false;
162 			    }
163 			} catch (NumberFormatException e) {
164 			    // The last part can be a standard IPv4 address.
165 			    if (i != parts.length - 1 || !isIPv4(firstChar, part)) {
166 				return false;
167 			    }
168 			    ipv4Mapped = true;
169 			}
170 		    }
171 
172 		    // IPv6 addresses are 128 bit, are we that long?
173 		    if (zeroSubstitution) {
174 			if (ipv4Mapped) {
175 			    return checkIPv4MappedIPv6 && 1 <= count && count <= 6;
176 			} else {
177 			    return checkIPv6 && 1 <= count && count <= 7;
178 			}
179 		    } else {
180 			if (ipv4Mapped) {
181 			    return checkIPv4MappedIPv6 && count == 7;
182 			} else {
183 			    return checkIPv6 && count == 8;
184 			}
185 		    }
186 		} else {
187 		    return false;
188 		}
189     }
190 
191 
192 	public boolean hasChosenAddressTypes() {
193 		return getProperty(CHECK_ADDRESS_TYPES_DESCRIPTOR).length > 0;
194 	}
195 
196 	/**
197 	 * @see PropertySource#dysfunctionReason()
198 	 */
199 	@Override
200 	public String dysfunctionReason() {
201 		return hasChosenAddressTypes() ?
202 				null :
203 				"No address types specified";
204 	}
205 }