1
2
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
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
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
69 final String image = node.getImage().substring(1, node.getImage().length() - 1);
70
71
72
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
92
93
94
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
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
116
117
118
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
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
136 if (s.endsWith(":")) {
137 return false;
138 }
139
140
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
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
158 try {
159 int value = Integer.parseInt(part, 16);
160 if (value < 0 || value > 65535) {
161 return false;
162 }
163 } catch (NumberFormatException e) {
164
165 if (i != parts.length - 1 || !isIPv4(firstChar, part)) {
166 return false;
167 }
168 ipv4Mapped = true;
169 }
170 }
171
172
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
198
199 @Override
200 public String dysfunctionReason() {
201 return hasChosenAddressTypes() ?
202 null :
203 "No address types specified";
204 }
205 }