Почему в UTF-8 диапазон двухбайтовых кодов начинается с C2 80, а не с C0 80? Объяснение ошибки «overlong encoding» и гарантии самосинхронизации
UTF-8 — универсальный стандарт кодирования символов, который использует от 1 до 4 байтов для представления символов Юникода. При этом для кодов от U+0080
до U+07FF
предусмотрено использование именно двухбайтовых последовательностей. Возникает вопрос: почему допустимый диапазон двухбайтовых кодов начинается не с байта C0 80
, а с C2 80
? Почему первые два возможных префиксных байта C0
и C1
считаются недопустимыми, хотя формально они бы кодировали значения из этого диапазона?
Проблема: что такое «overlong encoding» и как связана с «самосинхронизацией»?
Рассмотрим подробнее причину. UTF-8 строится так, чтобы каждый символ имел лишь одно уникальное кодирование, и чтобы поток байтов можно было читать надёжно, даже если начало чтения смещено на середину последовательности. Важные свойства UTF-8:
- Уникальность кодировки: каждый символ Unicode представлен одним и только одним набором байтов.
- Самосинхронизация: если процесс чтения байтов начнётся внутри многобайтового кода (например, со второй или третьей части), декодер легко найдёт начало следующего символа.
Если бы двухбайтовые коды могли начинаться с C0 80
или C1 xx
, это позволило бы создавать overlong encodings — ситуаци, когда символ мог быть записан с избыточным числом байтов, например:
C0 80 — кодирование символа U+0000 (нуль согласно стандарту),
хотя для U+0000 корректным является один байт 00.
Подобные overlong encoding опасны, потому что они нарушают однозначность кодировки. Это создаёт проблемы безопасности, особенно в системах, где проверка на валидность символов или их фильтрация зависят от правильной декодировки.
Почему в UTF-8 запрещены C0 и C1?
Два первых байта в диапазоне C0
и C1
соответствуют четырём самым младшим битам кодовой точки. Но, если использовать байты C0
или C1
с продолжением 10xxxxxx
(формат UTF-8), они приводят к «overlong» кодировке символов с кодами, которые можно было бы записать короче.
Поэтому в спецификации UTF-8 байты C0
и C1
считаются запрещёнными как начальные байты двухбайтовых последовательностей. Это помогает гарантировать:
- Уникальность каждого кодирования символа.
- Отсутствие проблем с переполнением при проверке кодов.
Как работает «самосинхронизация» в UTF-8?
Самосинхронизация означает, что если при чтении байтов поток начинается не с начала символа, а с середины (то есть с «продолжающего» байта, у которого старшие два бита 10
), декодер сможет найти начало следующего правильного символа, ориентируясь на байты с паттернами 0xxxxxxx
(однобайтный символ) и 11xxxxxx
(начальный байт многобайтного символа).
Но если бы разрешить C0 80
и C1 xx
, то возможны неоднозначности и нарушения правил синхронизации при ошибках в потоке. Запрет этих значений избавляет от лишних погрешностей и повышает устойчивость к ошибкам передачи.
Резюме: варианты и рекомендации
Вариант 1: начинать диапазон двухбайтовых кодов с C0 80
, разрешая overlong encodings.
- Это может привести к неоднозначной и небезопасной кодировке символов.
- Нарушает уникальность представления и усложняет проверку на валидность.
- Может ухудшить защиту от уязвимостей типа инъекций или обхода фильтров.
Вариант 2 (стандартный): начинать с C2 80
, исключая C0
и C1
.
- Гарантирует уникальность кодировки каждого символа.
- Повышает точность и устойчивость декодера.
- Обеспечивает защиту от атак, связанных с overlong encoding.
- Поддерживает самосинхронизацию и надёжность обработки данных.
Выводы
Исключение первых двух возможных начальных байтов C0
и C1
как валидных для двухбайтовых кодов — это осознанное решение в стандарте UTF-8. Оно направлено на предотвращение overlong encodings, которые не гарантируют однозначности и безопасности обработки текста. Такой подход обеспечивает стабильную и предсказуемую работу систем, где применяется UTF-8, а также упрощает обработку ошибок и повышает надёжность «самосинхронизации» при чтении данных.
Если вы работаете с проверкой или синтезом UTF-8, строго придерживайтесь стандарта, запрещающего C0
и C1
в начале двухбайтовых последовательностей, чтобы обеспечить безопасность и совместимость вашего кода.