Unicode в текстовых данных представляет собой систему кодирования, в которой один и тот же визуальный символ может иметь несколько различных бинарных представлений. Это создаёт фундаментальную проблему для любой валидации строк: формально разные последовательности символов могут выглядеть одинаково, но сравниваться как разные значения.
В Unicode один и тот же символ может быть записан по-разному.
Например, символ «é» может быть представлен как единый кодовый пункт
U+00E9, либо как комбинация базовой латинской буквы
e (U+0065) и комбинирующего акцента
U+0301. Визуально эти варианты идентичны, но побайтно
различаются.
Такая особенность критична для систем валидации, поскольку проверка строк без приведения к единому виду приводит к ложным несовпадениям, дубликатам и потенциальным уязвимостям.
Стандарт Unicode определяет несколько форм нормализации, каждая из которых решает задачу приведения строк к каноническому виду.
Композиционная форма, при которой символы приводятся к их составному
виду. Например, комбинация e + ́ преобразуется в
é.
Декомпозиционная форма, при которой составные символы разбиваются на базовые элементы и диакритические знаки.
Совместимые формы нормализации, которые дополнительно учитывают совместимость символов. Например, символы математических обозначений или полноширинные символы могут быть приведены к базовым ASCII-эквивалентам.
Разница между NFC/NFD и NFKC/NFKD принципиальна: первые сохраняют семантику символов, вторые — нормализуют с учётом визуальной и функциональной эквивалентности.
В JavaScript встроен механизм нормализации строк:
const str = "e\u0301"; // e + combining acute accent
str.normalize('NFC'); // "é"
str.normalize('NFD'); // "e\u0301"
str.normalize('NFKC'); // совместимая форма
str.normalize('NFKD'); // совместимая декомпозиция
Метод normalize() является базовым инструментом для
унификации строк перед сравнением, поиском и валидацией.
Любая библиотека валидации строк сталкивается с проблемой эквивалентности представлений. Validator.js, как инструмент проверки и очистки входных данных, опирается на предположение, что строка уже приведена к согласованному виду.
Без нормализации возможны следующие проблемы:
Библиотека 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-адреса исторически ограничены ASCII, однако современные стандарты (EAI — Email Address Internationalization) допускают использование Unicode. Несмотря на это, большинство систем и валидаторов, включая Validator.js, ориентируются на ASCII-совместимую модель.
Проблема возникает в доменной части, где интернационализированные домены (IDN) могут быть представлены как в Unicode, так и в Punycode:
// Unicode домен
user@пример.рф
// Punycode представление
user@xn--e1afmkfd.xn--p1ai
Без нормализации такие адреса будут восприниматься как разные строки.
В 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 обычно включает последовательность:
function prepare(value) {
return value
.normalize('NFC')
.trim();
}
validator.isEmail(prepare(input));
Такой подход снижает вероятность расхождения между визуальным и фактическим представлением данных.
Validator.js не решает задачу Unicode-эквивалентности полностью, поскольку это выходит за рамки прикладной валидации строк. Нормализация остаётся обязанностью слоя обработки данных до передачи их в валидатор.
Особенно критичны сценарии:
Unicode-строки без нормализации нарушают детерминированность этих операций, приводя к неоднозначным результатам.