Нормализация Unicode

Unicode в текстовых данных представляет собой систему кодирования, в которой один и тот же визуальный символ может иметь несколько различных бинарных представлений. Это создаёт фундаментальную проблему для любой валидации строк: формально разные последовательности символов могут выглядеть одинаково, но сравниваться как разные значения.

В Unicode один и тот же символ может быть записан по-разному. Например, символ «é» может быть представлен как единый кодовый пункт U+00E9, либо как комбинация базовой латинской буквы e (U+0065) и комбинирующего акцента U+0301. Визуально эти варианты идентичны, но побайтно различаются.

Такая особенность критична для систем валидации, поскольку проверка строк без приведения к единому виду приводит к ложным несовпадениям, дубликатам и потенциальным уязвимостям.

Формы нормализации Unicode

Стандарт Unicode определяет несколько форм нормализации, каждая из которых решает задачу приведения строк к каноническому виду.

NFC (Normalization Form C)

Композиционная форма, при которой символы приводятся к их составному виду. Например, комбинация e + ́ преобразуется в é.

NFD (Normalization Form D)

Декомпозиционная форма, при которой составные символы разбиваются на базовые элементы и диакритические знаки.

NFKC и NFKD

Совместимые формы нормализации, которые дополнительно учитывают совместимость символов. Например, символы математических обозначений или полноширинные символы могут быть приведены к базовым ASCII-эквивалентам.

Разница между NFC/NFD и NFKC/NFKD принципиальна: первые сохраняют семантику символов, вторые — нормализуют с учётом визуальной и функциональной эквивалентности.

Нормализация в JavaScript

В JavaScript встроен механизм нормализации строк:

const str = "e\u0301"; // e + combining acute accent

str.normalize('NFC');  // "é"
str.normalize('NFD');  // "e\u0301"
str.normalize('NFKC'); // совместимая форма
str.normalize('NFKD'); // совместимая декомпозиция

Метод normalize() является базовым инструментом для унификации строк перед сравнением, поиском и валидацией.

Влияние Unicode на валидацию данных

Любая библиотека валидации строк сталкивается с проблемой эквивалентности представлений. Validator.js, как инструмент проверки и очистки входных данных, опирается на предположение, что строка уже приведена к согласованному виду.

Без нормализации возможны следующие проблемы:

  • ложные несовпадения email-адресов;
  • обход проверок длины строки;
  • подмена визуально идентичных символов;
  • обход фильтров запрещённых символов;
  • дублирование записей в базах данных.

Validator.js и обработка Unicode

Библиотека Validator.js предоставляет набор функций для проверки и преобразования строк, но не выполняет полноценную Unicode-нормализацию автоматически. Это означает, что ответственность за приведение строки к нормализованному виду лежит на уровне подготовки данных.

Типичный набор функций, связанных с обработкой строк:

  • isEmail — проверка корректности email;
  • normalizeEmail — приведение email к каноническому виду;
  • isURL — проверка URL;
  • isLength — проверка длины строки;
  • trim, escape, unescape — очистка строк.

При этом normalizeEmail частично решает задачу нормализации, но исключительно в контексте email-адресов (например, приведение доменной части к нижнему регистру, удаление точек в Gmail-адресах).

Практика предварительной нормализации

Валидация строк, содержащих Unicode-символы, требует предварительного приведения к единой форме:

import validator from 'validator';

function validateInput(value) {
  const normalized = value.normalize('NFC');

  return validator.isLength(normalized, { min: 3, max: 50 });
}

Приведение к NFC используется как наиболее универсальный вариант, поскольку он соответствует каноническому представлению текста в большинстве современных систем.

Email-адреса и Unicode

Email-адреса исторически ограничены ASCII, однако современные стандарты (EAI — Email Address Internationalization) допускают использование Unicode. Несмотря на это, большинство систем и валидаторов, включая Validator.js, ориентируются на ASCII-совместимую модель.

Проблема возникает в доменной части, где интернационализированные домены (IDN) могут быть представлены как в Unicode, так и в Punycode:

// Unicode домен
user@пример.рф

// Punycode представление
user@xn--e1afmkfd.xn--p1ai

Без нормализации такие адреса будут восприниматься как разные строки.

URL и Unicode

В URL также используется интернационализация через IDN и percent-encoding. Validator.js при проверке URL ориентируется на стандартизированные формы, но не преобразует Unicode автоматически.

validator.isURL('https://пример.рф');
validator.isURL('https://xn--e1afmkfd.xn--p1ai');

Обе формы могут быть валидны, но их эквивалентность зависит от предварительной нормализации и преобразования.

Конфузабельные символы и безопасность

Unicode содержит большое количество символов, визуально неотличимых друг от друга (homoglyphs). Например:

  • латинская a и кириллическая а;
  • латинская e и греческая ε;
  • цифра 0 и буква O.

Эти различия критичны для систем аутентификации и проверки идентификаторов. Без нормализации и дополнительной фильтрации возможно создание строк, визуально идентичных уже существующим, но технически различающихся.

Стратегия нормализации перед валидацией

Корректная схема обработки строк в контексте Validator.js обычно включает последовательность:

  1. Приведение строки к Unicode NFC;
  2. Удаление управляющих символов;
  3. Приведение к нижнему регистру (при необходимости);
  4. Применение функций Validator.js;
  5. Дополнительная проверка доменных или контекстных правил.
function prepare(value) {
  return value
    .normalize('NFC')
    .trim();
}

validator.isEmail(prepare(input));

Такой подход снижает вероятность расхождения между визуальным и фактическим представлением данных.

Ограничения автоматической валидации

Validator.js не решает задачу Unicode-эквивалентности полностью, поскольку это выходит за рамки прикладной валидации строк. Нормализация остаётся обязанностью слоя обработки данных до передачи их в валидатор.

Особенно критичны сценарии:

  • сравнение идентификаторов пользователей;
  • поиск по базе данных;
  • проверка уникальности email и username;
  • обработка пользовательского ввода в формах.

Unicode-строки без нормализации нарушают детерминированность этих операций, приводя к неоднозначным результатам.