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[] { 0, 1, 2 }, 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() < 7 || !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 < 0 || 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() < 3 || !(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 < 0 || 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 - 1 || !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 && 1 <= count && count <= 6;
173 } else {
174 return checkIPv6 && 1 <= 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 }
|