Регулярное выражение (RegEx) для IPv6 Отдельно от IPv4

Пожалуйста, прочитайте, прежде чем отмечать дубликаты

Мне не удалось создать или найти RegEx, который работает для всех форматов IPv6 (мои тестовые примеры ниже). Я знаю об этом вопросе, который каждый указывает на: Регулярное выражение, которое соответствует действительным адресам IPv6 Однако все они объединяют IPv6 с IPv4 и/или не работают со всем моим тестом случаев.

Требования:

  • Я не хочу, чтобы он также проверял значения IPv4, у меня уже есть отдельная функция проверки для IPv4.
  • Мне нужен шаблон, который работает в Coldfusion и шаблон, который работает в PL/SQL.
  • Поскольку я использую его в PL/SQL, шаблон для него должен оставаться под 512 символами. И Oracle поддерживает только узкую часть языка RegExp. Таким образом, шаблон Coldfusion может отличаться от шаблона PL/SQL, это нормально, если они оба работают.
  • Конечный результат не должен быть длинным RegEx, его можно разделить.

Вот последний образец, который я пробовал:

^(?>(?>([a-f0-9]{1,4})(?>:(?1)){7}|(?!(?:.*[a-f0-9](?>:|$)){8,})((?1)(?>:(?1)){0,6})?::(?2)?)|(?>(?>(?1)(?>:(?1)){5}:|(?!(?:.*[a-f0-9]:){6,})(?3)?::(?>((?1)(?>:(?1)){0,4}):)?)?(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?>\.(?4)){3}))$

Это приближается для Coldfusion, но не 100%. Это не работает вообще в PL/SQL.

Результаты тестов http://regex101.com/r/wI8cI0 Элементы полужирный - это те, не работает в Coldfusion:

  • матч
  • матч
  • матч
  • матч
  • матч
  • соответствие (но @Michael Hampton говорит, что это не должно совпадать, потому что это не действительный IPv6-адрес, но другие сказали мне, что он действителен, поэтому я не уверен в этом случае.)
  • match (:: - действительно действительный формат, спасибо @Sander Steffann.)
  • матч
  • нет соответствия
  • матч
  • нет соответствия
  • нет соответствия
  • нет соответствия
  • матч
  • матч
  • нет соответствия
  • нет соответствия
  • нет соответствия
  • нет соответствия

Я получил тестовые примеры 8-11: http://publib.boulder.ibm.com/infocenter/iseries/v5r3/index.jsp?topic=%2Frzai2%2Frzai2ipv6addrformat.htm И сказали: Test 9 и 11 предназначены для префикса адреса IPv6, а не для IPv6-адреса, поэтому они не должны совпадать.

Конечный результат, мне нужно, чтобы они работали в таких выражениях:

ColdFusion:

<cfset IndexOfOccurrence1=REFind("^(?>(?>([a-f0-9]{1,4})(?>:(?1)){7}|(?!(?:.*[a-f0-9](?>:|$)){8,})((?1)(?>:(?1)){0,6})?::(?2)?)|(?>(?>(?1)(?>:(?1)){5}:|(?!(?:.*[a-f0-9]:){6,})(?3)?::(?>((?1)(?>:(?1)){0,4}):)?)?(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?>\.(?4)){3}))$",value[i])>

PL/SQL:

if ( REGEXP_LIKE(v,'^(?>(?>([a-f0-9]{1,4})(?>:(?1)){7}|(?!(?:.*[a-f0-9](?>:|$)){8,})((?1)(?>:(?1)){0,6})?::(?2)?)|(?>(?>(?1)(?>:(?1)){5}:|(?!(?:.*[a-f0-9]:){6,})(?3)?::(?>((?1)(?>:(?1)){0,4}):)?)?(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(?>\.(?4)){3}))$','i') ) then

Ответ 1

С большой помощью от @nhahtdh в этом ответе fooobar.com/questions/19892/... Я обнаружил, что это лучшее решение. Ниже приведен пример того, как это сделать в PL/SQL, но это можно сделать на других языках. Я сделаю то же самое в ColdFusion. Для PL/SQL шаблон должен оставаться под 512 символами, поэтому его разбиение отлично работает, и его просто понять. Он передал все мои тестовые примеры в исходном вопросе.

if (
    /* IPv6 expanded */
    REGEXP_LIKE(v, '\A[[:xdigit:]]{1,4}(:[[:xdigit:]]{1,4}){7}\z')
    /* IPv6 shorthand */
    OR (NOT REGEXP_LIKE(v, '\A(.*?[[:xdigit:]](:|\z)){8}')
    AND REGEXP_LIKE(v, '\A([[:xdigit:]]{1,4}(:[[:xdigit:]]{1,4}){0,6})?::([[:xdigit:]]{1,4}(:[[:xdigit:]]{1,4}){0,6})?\z'))
    /* IPv6 dotted-quad notation, expanded */
    OR REGEXP_LIKE(v, '\A[[:xdigit:]]{1,4}(:[[:xdigit:]]{1,4}){5}:(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}\z')
    /* IPv6 dotted-quad notation, shorthand */
    OR (NOT REGEXP_LIKE(v, '\A(.*?[[:xdigit:]]:){6}')
    AND REGEXP_LIKE(v, '\A([[:xdigit:]]{1,4}(:[[:xdigit:]]{1,4}){0,4})?::([[:xdigit:]]{1,4}:){0,5}(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9]?[0-9])){3}\z'))
) then

Ответ 2

Насколько я знаю, RegEx не работает для всех форматов IPv6. Даже есть, он настолько сложный и жесткий для поддержания (не легко читаемый). Кроме того, это может вызвать проблемы с производительностью. Поэтому я решил написать для этого метод (функцию). Вы можете легко добавить любые особые случаи, как пожелаете. Я написал это на С#, но я думаю, что вы можете преобразовать этот алгоритм на любой язык:

class IPv6Validator
{
    string charValidator = @"[A-Fa-f0-9]";
    string IPv4Validation = @"^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$";

    public bool IsIPv6(string maybeIPv6)
    {
        if (maybeIPv6 == "::")
        {
            return true;
        }

        int numberOfEmptyDigitGroups = 0;
        int expectedDigitGroupsLength = 8;
        string[] arrMaybeIPv6 = maybeIPv6.Split(':');

        if (arrMaybeIPv6.Length > 9 || arrMaybeIPv6.Length < 3)
        {
            return false;
        }

        for (int i = 0; i < arrMaybeIPv6.Length; i++)
        {
            //IF IPv6 starts or ends with "::" (ex ::1)
            if ((i == 0 || i == arrMaybeIPv6.Length - 2) && IsEmptyDigitGroup(arrMaybeIPv6[i]) && IsEmptyDigitGroup(arrMaybeIPv6[i+1]))
            {
                expectedDigitGroupsLength = 9;
                numberOfEmptyDigitGroups++;
                i++;
            }
            else if (arrMaybeIPv6[i].Trim() == string.Empty) //If IPv6 contains :: (ex 1:2::3)
            {
                numberOfEmptyDigitGroups++;
            }

            //Cannot have more than one "::"  (ex ::1:2::3)
            if (numberOfEmptyDigitGroups > 1)
            {
                return false;
            }

            //Mapped IPv4 control
            if (i == arrMaybeIPv6.Length - 1 && IsIPv4(arrMaybeIPv6[i]) && arrMaybeIPv6.Length < 8)
            {
                return true;
            }
            else if (i == arrMaybeIPv6.Length - 1 && HasSpecialCharInIPv6(arrMaybeIPv6[i], IsEmptyDigitGroup(arrMaybeIPv6[i - 1]))) //If last digit group contains special char (ex fe80::3%eth0)
            {
                return true;
            }
            else //if not IPV4, check the digits
            {
                //Cannot have more than 4 digits (ex 12345:1::)
                if (arrMaybeIPv6[i].Length > 4)
                {
                    return false;
                }

                //Check if it has unvalid char
                foreach (char ch in arrMaybeIPv6[i])
                {
                    if (!IsIPv6Char(ch.ToString()))
                    {
                        return false;
                    }
                }
            }

            //Checks if it has extra digit (ex 1:2:3:4:5:6:7:8f:)
            if (i >= expectedDigitGroupsLength)
            {
                return false;
            }

            //If it has missing digit at last or end (ex 1:2:3:4:5:6:7:)
            if ((i == 0 || i == arrMaybeIPv6.Length - 1) && IsEmptyDigitGroup(arrMaybeIPv6[i]) && expectedDigitGroupsLength != 9)
            {
                return false;
            }

            //If it has missing digits (ex 1:2:3:4:5:6)
            if (i == arrMaybeIPv6.Length - 1 && numberOfEmptyDigitGroups == 0 && arrMaybeIPv6.Length < 8)
            {
                return false;
            }
        }

        return true;
    }

    bool IsIPv4(string lastDigitGroup)
    {
        //If lastDigitGroup has special char, then get the first group for IPV4 validation (ex ::123.12.2.1/60)
        string maybeIPv4 = lastDigitGroup.Split('/','%')[0];

        Match match = Regex.Match(maybeIPv4, IPv4Validation);
        return match.Success;
    }

    bool IsIPv6Char(string strChar)
    {
        Match match = Regex.Match(strChar, charValidator);
        return match.Success;
    }

    bool IsSpecialChar(char ch)
    {
        if (ch == '%' || ch == '/')
        {
            return true;
        }
        return false;
    }

    bool HasSpecialCharInIPv6(string lastDigitGroup, bool isPreviousDigitGroupEmpty)
    {
        for (int i = 0; i < lastDigitGroup.Length; i++)
        {
            //If cannot find any special char at first 5 chars then leave the for loop
            if (i == 5)
                break;

            //If the first digit is special char, check the previous digits to be sure it is a valid IPv6 (ex FE80::/10)
            if (i == 0 && IsSpecialChar(lastDigitGroup[i]) && isPreviousDigitGroupEmpty)
                return true;

            if (i != 0 && IsSpecialChar(lastDigitGroup[i]))
                return true;

            if (!IsIPv6Char(lastDigitGroup[i].ToString()))
                return false;
        }
        return false;
    }

    bool IsEmptyDigitGroup(string digitGroup)
    {
        if (digitGroup.Trim() == string.Empty)
            return true;

        return false;
    }

}

Я также добавил другие методы, например, как искать IPv6 в тексте или файле. Вы можете проверить: Регулярное выражение, которое соответствует допустимым адресам IPv6

Редактировать резюме: сопоставлены Ipv4 и специальные символы, такие как ":: 123.23.23.23", "fe80:: 3% eth0", ":: ffff: 192.1.56.10/96".

Ответ 3

:: - действительный IPv6-адрес (адрес all-zeroes), поэтому почему бы не принять его?

И если вы не хотите принимать адреса IPv6 с последними 32 битами, записанными в нотации IPv4 (почему бы вам, они не являются действительными адресами), то просто отмените последнюю часть регулярного выражения, которое с ними связано ( начиная с ::(ffff).

Во всяком случае, регулярное выражение действительно содержит несколько ошибок в части нотации IPv4. Нотация IPv4 - это просто другой способ записать последние 32 бита адреса IPv6, и регулярное выражение не обрабатывает все допустимые варианты этого. Кроме того, он даже забывает избежать ., поэтому он также примет множество недопустимых строк.