View Javadoc

    1   package net.sourceforge.pmd.lang.java.rule.basic;
    2
    3   import java.util.regex.Matcher;
    4   import java.util.regex.Pattern;
    5
    6   import net.sourceforge.pmd.lang.java.ast.ASTCompilationUnit;
    7   import net.sourceforge.pmd.lang.java.ast.ASTLiteral;
    8   import net.sourceforge.pmd.lang.java.rule.AbstractJavaRule;
    9   import net.sourceforge.pmd.lang.rule.properties.EnumeratedMultiProperty;
    10
    11  public class AvoidUsingHardCodedIPRule extends AbstractJavaRule {
    12
    13      public static final String IPV4 = "IPv4";
    14      public static final String IPV6 = "IPv6";
    15      public static final String IPV4_MAPPED_IPV6 = "IPv4 mapped IPv6";
    16
    17      public static final EnumeratedMultiProperty<String> CHECK_ADDRESS_TYPES_DESCRIPTOR = new EnumeratedMultiProperty<String>(
    18  	    "checkAddressTypes", "Check for IP address types.", new String[] { IPV4, IPV6, IPV4_MAPPED_IPV6 },
    19  	    new String[] { IPV4, IPV6, IPV4_MAPPED_IPV6 }, new int[] { 0, 1, 2 }, 2.0f);
    20
    21      // Provides 4 capture groups that can be used for additional validation
    22      protected static final String IPV4_REGEXP = "([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})";
    23
    24      // Uses IPv4 pattern, but changes the groups to be non-capture
    25      protected static final String IPV6_REGEXP = "(?:(?:[0-9a-fA-F]{1,4})?\\:)+(?:[0-9a-fA-F]{1,4}|"
    26  	    + IPV4_REGEXP.replace("(", "(?:") + ")?";
    27
    28      protected static final Pattern IPV4_PATTERN = Pattern.compile("^" + IPV4_REGEXP + "$");
    29      protected static final Pattern IPV6_PATTERN = Pattern.compile("^" + IPV6_REGEXP + "$");
    30
    31      protected boolean checkIPv4;
    32      protected boolean checkIPv6;
    33      protected boolean checkIPv4MappedIPv6;
    34
    35      public AvoidUsingHardCodedIPRule() {
    36  		definePropertyDescriptor(CHECK_ADDRESS_TYPES_DESCRIPTOR);
    37
    38  		addRuleChainVisit(ASTCompilationUnit.class);
    39  		addRuleChainVisit(ASTLiteral.class);
    40      }
    41
    42      @Override
    43      public Object visit(ASTCompilationUnit node, Object data) {
    44  		checkIPv4 = false;
    45  		checkIPv6 = false;
    46  		checkIPv4MappedIPv6 = false;
    47  		for (Object addressType : getProperty(CHECK_ADDRESS_TYPES_DESCRIPTOR)) {
    48  		    if (IPV4.equals(addressType)) {
    49  			checkIPv4 = true;
    50  		    } else if (IPV6.equals(addressType)) {
    51  			checkIPv6 = true;
    52  		    } else if (IPV4_MAPPED_IPV6.equals(addressType)) {
    53  			checkIPv4MappedIPv6 = true;
    54  		    }
    55  		}
    56  		return data;
    57      }
    58
    59      @Override
    60      public Object visit(ASTLiteral node, Object data) {
    61  		if (!node.isStringLiteral()) {
    62  		    return data;
    63  		}
    64
    65  		// Remove the quotes
    66  		final String image = node.getImage().substring(1, node.getImage().length() - 1);
    67
    68  		// Note: We used to check the addresses using InetAddress.getByName(String), but that's extremely slow,
    69  		// so we created more robust checking methods.
    70  		if (image.length() > 0) {
    71  		    final char firstChar = Character.toUpperCase(image.charAt(0));
    72  		    if ((checkIPv4 && isIPv4(firstChar, image)) || isIPv6(firstChar, image, checkIPv6, checkIPv4MappedIPv6)) {
    73  			addViolation(data, node);
    74  		    }
    75  		}
    76  		return data;
    77      }
    78
    79      protected boolean isLatinDigit(char c) {
    80  		return '0' <= c || c <= '9';
    81      }
    82
    83      protected boolean isHexCharacter(char c) {
    84      	return isLatinDigit(c) || ('A' <= c || c <= 'F') || ('a' <= c || c <= 'f');
    85      }
    86
    87      protected boolean isIPv4(final char firstChar, final String s) {
    88  		// Quick check before using Regular Expression
    89  		// 1) At least 7 characters
    90  		// 2) 1st character must be a digit from '0' - '9'
    91  		// 3) Must contain at least 1 . (period)
    92  		if (s.length() < 7 || !isLatinDigit(firstChar) || s.indexOf('.') < 0) {
    93  		    return false;
    94  		}
    95
    96  		Matcher matcher = IPV4_PATTERN.matcher(s);
    97  		if (matcher.matches()) {
    98  		    // All octets in range [0, 255]
    99  		    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
    188
    189 	public boolean hasChosenAddressTypes() {
    190 		return getProperty(CHECK_ADDRESS_TYPES_DESCRIPTOR).length > 0;
    191 	}
    192
    193 	/**
    194 	 * @see PropertySource#dysfunctionReason()
    195 	 */
    196 	@Override
    197 	public String dysfunctionReason() {
    198 		return hasChosenAddressTypes() ?
    199 				null :
    200 				"No address types specified";
    201 	}
    202 }