19 package org.sleuthkit.autopsy.keywordsearch;
 
   21 import com.google.common.base.CharMatcher;
 
   22 import com.google.common.collect.ImmutableSet;
 
   23 import com.google.common.collect.Range;
 
   24 import com.google.common.collect.RangeMap;
 
   25 import com.google.common.collect.TreeRangeMap;
 
   27 import org.apache.commons.lang3.StringUtils;
 
   28 import org.apache.commons.validator.routines.checkdigit.LuhnCheckDigit;
 
   43 final class CreditCardValidator {
 
   45     private CreditCardValidator() {
 
   48     private static final LuhnCheckDigit CREDIT_CARD_NUM_LUHN_CHECK = 
new LuhnCheckDigit();
 
   53     static private final RangeMap<Integer, Set<Integer>> allowedLengths = TreeRangeMap.create();
 
   54     private static final ImmutableSet<Integer> Set12to19 = ImmutableSet.of(12, 13, 14, 15, 16, 17, 18, 19);
 
   55     private static final ImmutableSet<Integer> Set14to19 = ImmutableSet.of(14, 15, 16, 17, 18, 19);
 
   56     private static final ImmutableSet<Integer> Set16to19 = ImmutableSet.of(16, 17, 18, 29);
 
   60         allowedLengths.put(Range.closedOpen(34000000, 35000000), ImmutableSet.of(15));
 
   61         allowedLengths.put(Range.closedOpen(37000000, 38000000), ImmutableSet.of(15));
 
   64         allowedLengths.put(Range.closedOpen(40000000, 50000000), Set12to19);
 
   67         allowedLengths.put(Range.closedOpen(40260000, 40270000), ImmutableSet.of(16));
 
   68         allowedLengths.put(Range.closedOpen(41750000, 41750100), ImmutableSet.of(16));
 
   69         allowedLengths.put(Range.closedOpen(44050000, 44060000), ImmutableSet.of(16));
 
   70         allowedLengths.put(Range.closedOpen(45080000, 45090000), ImmutableSet.of(16));
 
   71         allowedLengths.put(Range.closedOpen(48440000, 48450000), ImmutableSet.of(16));
 
   72         allowedLengths.put(Range.closedOpen(49130000, 49140000), ImmutableSet.of(16));
 
   73         allowedLengths.put(Range.closedOpen(49170000, 49180000), ImmutableSet.of(16));
 
   76         allowedLengths.put(Range.closedOpen(62000000, 63000000), Set16to19);
 
   79         allowedLengths.put(Range.closedOpen(51000000, 56000000), ImmutableSet.of(16));
 
   80         allowedLengths.put(Range.closedOpen(22210000, 27210000), ImmutableSet.of(16));
 
   83         allowedLengths.put(Range.closedOpen(50609900, 50619900), ImmutableSet.of(16, 19));
 
   84         allowedLengths.put(Range.closedOpen(65000200, 65002700), ImmutableSet.of(16, 19));
 
   87         allowedLengths.put(Range.closedOpen(50000000, 50100000), Set12to19);
 
   88         allowedLengths.put(Range.closedOpen(56000000, 59000000), Set12to19);
 
   89         allowedLengths.put(Range.closedOpen(60000000, 70000000), Set12to19);
 
   90         allowedLengths.put(Range.closedOpen(63900000, 63910000), Set12to19);
 
   91         allowedLengths.put(Range.closedOpen(67000000, 68000000), Set12to19);
 
   94         allowedLengths.put(Range.closedOpen(30000000, 30600000), Set16to19);
 
   95         allowedLengths.put(Range.closedOpen(30950000, 30960000), Set16to19);
 
   96         allowedLengths.put(Range.closedOpen(36000000, 37000000), Set14to19);
 
   97         allowedLengths.put(Range.closedOpen(38000000, 40000000), Set16to19);
 
  100         allowedLengths.put(Range.closedOpen(54000000, 56000000), Set14to19);
 
  103         allowedLengths.put(Range.closedOpen(60110000, 60120000), Set16to19);
 
  104         allowedLengths.put(Range.closedOpen(62212600, 62292600), Set16to19);
 
  105         allowedLengths.put(Range.closedOpen(64400000, 66000000), Set16to19);
 
  108         allowedLengths.put(Range.closedOpen(35280000, 35900000), Set16to19);
 
  111         allowedLengths.put(Range.closedOpen(50190000, 50200000), Set16to19);
 
  114         allowedLengths.put(Range.closedOpen(63600000, 63700000), Set16to19);
 
  127     static public boolean isValidCCN(String rawCCN) {
 
  129         boolean hasSpace = StringUtils.contains(rawCCN, 
' ');
 
  130         boolean hasDash = StringUtils.contains(rawCCN, 
'-');
 
  131         if (hasSpace && hasDash) {
 
  135         Character separator = null;
 
  138         } 
else if (hasDash) {
 
  142         final String cannonicalCCN;
 
  144         if (separator != null) {
 
  146             cannonicalCCN = CharMatcher.anyOf(separator.toString()).removeFrom(rawCCN);
 
  147             splitCCN = rawCCN.split(separator.toString());
 
  150             cannonicalCCN = rawCCN;
 
  151             splitCCN = 
new String[]{cannonicalCCN};
 
  154         if (
false == lengthMatchesBin(cannonicalCCN)) {
 
  159         switch (cannonicalCCN.length()) {
 
  161                 if (
false == isValid15DigitGrouping(splitCCN)) {
 
  166                 if (
false == isValid16DigitGrouping(splitCCN)) {
 
  171                 if (
false == isValid19DigitGrouping(splitCCN)) {
 
  176                 if (
false == isValidOtherDigitGrouping(splitCCN)) {
 
  181         return CREDIT_CARD_NUM_LUHN_CHECK.isValid(cannonicalCCN);
 
  184     static private boolean lengthMatchesBin(String cannonicalCCN) {
 
  185         String BIN = cannonicalCCN.substring(0, 8);
 
  186         final Set<Integer> lengthsForBIN = allowedLengths.get(Integer.valueOf(BIN));
 
  187         return null == lengthsForBIN ||  lengthsForBIN.contains(cannonicalCCN.length());
 
  190     static private boolean isValidOtherDigitGrouping(String[] splitCCN) {
 
  191         if (splitCCN.length == 1) {
 
  194             return splitCCN[0].length() == 4;
 
  198     static private boolean isValid19DigitGrouping(String[] splitCCN) {
 
  199         switch (splitCCN.length) {
 
  203                 return splitCCN[0].length() == 6
 
  204                         && splitCCN[1].length() == 13;
 
  206                 return splitCCN[0].length() == 4
 
  207                         && splitCCN[1].length() == 4
 
  208                         && splitCCN[2].length() == 4
 
  209                         && splitCCN[3].length() == 4
 
  210                         && splitCCN[4].length() == 3;
 
  216     static private boolean isValid16DigitGrouping(String[] splitCCN) {
 
  217         switch (splitCCN.length) {
 
  221                 return splitCCN[0].length() == 4
 
  222                         && splitCCN[1].length() == 4
 
  223                         && splitCCN[2].length() == 4
 
  224                         && splitCCN[3].length() == 4;
 
  230     static private boolean isValid15DigitGrouping(String[] splitCCN) {
 
  231         switch (splitCCN.length) {
 
  235                 return (splitCCN[0].length() == 4 && splitCCN[1].length() == 6 && splitCCN[2].length() == 5);