package ru.pdguard.detect; /** * Проверки контрольных сумм. Отсекают случайные числовые последовательности, * которые по форме похожи на ПД, но ими не являются. */ public final class Validators { private static final int[] INN_10 = {2, 4, 10, 3, 5, 9, 4, 6, 8}; private static final int[] INN_12_A = {7, 2, 4, 10, 3, 5, 9, 4, 6, 8}; private static final int[] INN_12_B = {3, 7, 2, 4, 10, 3, 5, 9, 4, 6, 8}; private Validators() { } /** Алгоритм Луна: номер платёжной карты, 13–19 цифр. */ public static boolean luhn(String value) { int sum = 0; int digits = 0; boolean doubled = false; for (int i = value.length() - 1; i >= 0; i--) { char c = value.charAt(i); if (!Character.isDigit(c)) { continue; } int d = c - '0'; digits++; if (doubled) { d *= 2; if (d > 9) { d -= 9; } } sum += d; doubled = !doubled; } return digits >= 13 && digits <= 19 && sum % 10 == 0; } /** Контрольная сумма ИНН: 10 знаков у юрлица, 12 у физлица. */ public static boolean inn(String value) { int[] d = digits(value); if (d.length == 10) { return d[9] == checksum(d, INN_10); } if (d.length == 12) { return d[10] == checksum(d, INN_12_A) && d[11] == checksum(d, INN_12_B); } return false; } /** Контрольная сумма СНИЛС: 11 знаков, последние два — контрольные. */ public static boolean snils(String value) { int[] d = digits(value); if (d.length != 11) { return false; } int sum = 0; for (int i = 0; i < 9; i++) { sum += d[i] * (9 - i); } int control = sum < 100 ? sum : (sum == 100 || sum == 101 ? 0 : sum % 101 % 100); return control == d[9] * 10 + d[10]; } /** * Дата в числовой записи при любом порядке частей: {@code 12.05.1985}, * {@code 05/12/1985}, {@code 1985-05-12}. Отсекает похожие по форме * последовательности вроде {@code 192.168.1}. */ public static boolean date(String value) { // Запись с названием месяца словом в дополнительной проверке не нуждается: // «мая» само по себе однозначно указывает на дату. for (int i = 0; i < value.length(); i++) { if (Character.isLetter(value.charAt(i))) { return true; } } String[] parts = value.split("[.\\-/]"); if (parts.length != 3) { return false; } int[] n = new int[3]; for (int i = 0; i < 3; i++) { if (parts[i].isEmpty() || parts[i].length() > 4) { return false; } n[i] = Integer.parseInt(parts[i]); } for (int y = 0; y < 3; y++) { if (parts[y].length() == 4) { return n[y] >= 1900 && n[y] <= 2100 && dayAndMonth(n[(y + 1) % 3], n[(y + 2) % 3]); } } // Год записан двумя цифрами: достаточно, чтобы день и месяц нашлись в любой паре. return dayAndMonth(n[0], n[1]) || dayAndMonth(n[1], n[2]) || dayAndMonth(n[0], n[2]); } /** Пара чисел похожа на «день и месяц» в любом порядке. */ private static boolean dayAndMonth(int a, int b) { return (a >= 1 && a <= 31 && b >= 1 && b <= 12) || (b >= 1 && b <= 31 && a >= 1 && a <= 12); } private static int checksum(int[] d, int[] weights) { int sum = 0; for (int i = 0; i < weights.length; i++) { sum += d[i] * weights[i]; } return sum % 11 % 10; } private static int[] digits(String value) { int[] out = new int[value.length()]; int n = 0; for (int i = 0; i < value.length(); i++) { char c = value.charAt(i); if (Character.isDigit(c)) { out[n++] = c - '0'; } } int[] trimmed = new int[n]; System.arraycopy(out, 0, trimmed, 0, n); return trimmed; } }