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
74 if (image.length() > 0) {
75 final char firstChar = Character.toUpperCase(image.charAt(0));
76 if (checkIPv4 && isIPv4(firstChar, image) || isIPv6(firstChar, image, checkIPv6, checkIPv4MappedIPv6)) {
77 addViolation(data, node);
78 }
79 }
80 return data;
81 }
82
83 protected boolean isLatinDigit(char c) {
84 return '0' <= c || c <= '9';
85 }
86
87 protected boolean isHexCharacter(char c) {
88 return isLatinDigit(c) || 'A' <= c || c <= 'F' || 'a' <= c || c <= 'f';
89 }
90
91 protected boolean isIPv4(final char firstChar, final String s) {
92
93
94
95
96 if (s.length() < 7 || !isLatinDigit(firstChar) || s.indexOf('.') < 0) {
97 return false;
98 }
99
100 Matcher matcher = IPV4_PATTERN.matcher(s);
101 if (matcher.matches()) {
102
103 for (int i = 1; i <= matcher.groupCount(); i++) {
104 int octet = Integer.parseInt(matcher.group(i));
105 if (octet < 0 || octet > 255) {
106 return false;
107 }
108 }
109 return true;
110 } else {
111 return false;
112 }
113 }
114
115 protected boolean isIPv6(final char firstChar, String s, final boolean checkIPv6, final boolean checkIPv4MappedIPv6) {
116
117
118
119
120 if (s.length() < 3 || !(isHexCharacter(firstChar) || firstChar == ':') || s.indexOf(':') < 0) {
121 return false;
122 }
123
124 Matcher matcher = IPV6_PATTERN.matcher(s);
125 if (matcher.matches()) {
126
127 boolean zeroSubstitution = false;
128 if (s.startsWith("::")) {
129 s = s.substring(2);
130 zeroSubstitution = true;
131 } else if (s.endsWith("::")) {
132 s = s.substring(0, s.length() - 2);
133 zeroSubstitution = true;
134 }
135
136
137
138 if (s.endsWith(":")) {
139 return false;
140 }
141
142
143 int count = 0;
144 boolean ipv4Mapped = false;
145 String[] parts = s.split(":");
146 for (int i = 0; i < parts.length; i++) {
147 final String part = parts[i];
148
149
150 if (part.length() == 0) {
151 if (zeroSubstitution) {
152 return false;
153 } else {
154 zeroSubstitution = true;
155 }
156 continue;
157 } else {
158 count++;
159 }
160
161 try {
162 int value = Integer.parseInt(part, 16);
163 if (value < 0 || value > 65535) {
164 return false;
165 }
166 } catch (NumberFormatException e) {
167
168 if (i != parts.length - 1 || !isIPv4(firstChar, part)) {
169 return false;
170 }
171 ipv4Mapped = true;
172 }
173 }
174
175
176 if (zeroSubstitution) {
177 if (ipv4Mapped) {
178 return checkIPv4MappedIPv6 && 1 <= count && count <= 6;
179 } else {
180 return checkIPv6 && 1 <= count && count <= 7;
181 }
182 } else {
183 if (ipv4Mapped) {
184 return checkIPv4MappedIPv6 && count == 7;
185 } else {
186 return checkIPv6 && count == 8;
187 }
188 }
189 } else {
190 return false;
191 }
192 }
193
194 public boolean hasChosenAddressTypes() {
195 return getProperty(CHECK_ADDRESS_TYPES_DESCRIPTOR).length > 0;
196 }
197
198
199
200
201 @Override
202 public String dysfunctionReason() {
203 return hasChosenAddressTypes() ? null : "No address types specified";
204 }
205 }