Что такое процентное кодирование? (RFC 3986 простыми словами)
Процентное кодирование — это механизм, определённый в RFC 3986, для кодирования специальных символов в URI путём их замены на знак процента (%), за которым следуют две шестнадцатеричные цифры. Оно гарантирует, что URL-адреса содержат только допустимые ASCII-символы, которые можно безопасно передавать по интернету.
Что такое процентное кодирование?
Процентное кодирование, также называемое URL-кодированием, — это стандартный способ представления символов в URI (Uniform Resource Identifier, унифицированном идентификаторе ресурса), которые недопустимы или имеют специальное значение. Оно работает за счёт замены каждого символа, требующего кодирования, на знак процента (%), за которым следуют две шестнадцатеричные цифры, представляющие байтовое значение символа.
Например, символ пробела (байтовое значение 0x20) кодируется как %20. Знак «собака» @ (байтовое значение 0x40) кодируется как %40. Этот механизм гарантирует, что любые данные можно безопасно встроить в URI, не вступая в конфликт с синтаксисом URI.
Термин «процентное кодирование» происходит от использования символа процента в качестве экранирующего префикса. Формальная спецификация определена в RFC 3986, опубликованном Инженерным советом Интернета (IETF) в январе 2005 года; его авторы — Тим Бернерс-Ли, Рой Филдинг и Ларри Мазинтер.
Как работает процентное кодирование
Процесс кодирования состоит из следующих шагов: сначала символ преобразуется в его байтовое представление с использованием той или иной кодировки символов (на современном вебе практически всегда это UTF-8). Затем каждый байт представляется в виде знака процента, за которым следуют две шестнадцатеричные цифры (по соглашению используются заглавные буквы A-F, хотя декодеры должны принимать и строчные).
// Пошаговое кодирование символа пробела
// Символ: ' ' (пробел)
// Байтовое значение ASCII/UTF-8: 0x20 (десятичное 32)
// Процентное кодирование: %20
// Пошаговое кодирование многобайтового символа: e с акутом
// Символ: e с акутом (U+00E9)
// Байты UTF-8: 0xC3 0xA9
// Процентное кодирование: %C3%A9
// Пошаговое кодирование трёхбайтового символа
// Символ: иероглиф CJK (U+4E2D)
// Байты UTF-8: 0xE4 0xB8 0xAD
// Процентное кодирование: %E4%B8%AD
Шестнадцатеричные цифры всегда должны идти парами. Одиночный знак процента или знак процента, за которым следуют не шестнадцатеричные символы, недопустим и приведёт к ошибке декодирования. Это распространённая причина ошибки «URI malformed» в JavaScript.
Процентное кодирование всегда обратимо. Декодер считывает знак процента, берёт следующие два символа как шестнадцатеричное байтовое значение и преобразует его обратно в исходный символ. Для многобайтовых символов UTF-8 последовательно закодированные байты объединяются и декодируются вместе.
Какие символы должны кодироваться?
Не все символы требуют процентного кодирования. RFC 3986 определяет две категории: незарезервированные символы, которые никогда не нуждаются в кодировании, и зарезервированные символы, которые должны кодироваться, когда они не используются по своему зарезервированному назначению. Все остальные символы (включая пробелы, символы не из ASCII и управляющие символы) должны кодироваться всегда.
Символы, которые НЕ нуждаются в кодировании (незарезервированные): заглавные буквы (A-Z), строчные буквы (a-z), цифры (0-9), дефис (-), точка (.), символ подчёркивания (_) и тильда (~). Эти 66 символов могут появляться в любом месте URI без кодирования.
Символы, которые иногда нуждаются в кодировании (зарезервированные): : / ? # [ ] @ ! $ & ' ( ) * + , ; =. Эти символы имеют специальное синтаксическое значение в URI. Они нуждаются в кодировании только тогда, когда используются как данные, а не как разделители.
Символы, которые всегда нуждаются в кодировании: пробелы, символы не из ASCII (любой символ выше 127), управляющие символы (0–31 и 127) и небезопасные символы, такие как < > { } | \ ^ ` ".
Зарезервированные и незарезервированные символы в RFC 3986
Различие между зарезервированными и незарезервированными символами имеет основополагающее значение для того, как работают URI. Зарезервированные символы служат разделителями, которые определяют структуру URI. Например, :// отделяет схему от компонента authority, / разделяет сегменты пути, ? начинает строку запроса, а # начинает фрагмент.
| Категория | Символы | Когда кодировать |
|---|---|---|
| Незарезервированные | A-Z a-z 0-9 - . _ ~ |
Никогда |
| Общие разделители | : / ? # [ ] @ |
Когда используются как данные, а не как разделители |
| Субразделители | ! $ & ' ( ) * + , ; = |
Когда используются как данные в компонентах, придающих им значение |
| Все остальные символы | Пробелы, символы не из ASCII, управляющие символы и т. д. | Всегда |
Ключевой принцип RFC 3986 заключается в том, что URI, различающиеся только тем, закодирован ли зарезервированный символ процентным кодированием или присутствует буквально, не являются эквивалентными. Например, /path/to и /path%2Fto — это разные URI, даже несмотря на то, что %2F декодируется в /. У первого два сегмента пути; у второго — один сегмент пути, содержащий буквальный слеш.
Процентное кодирование и URL-кодирование: это одно и то же?
«Процентное кодирование» и «URL-кодирование» часто используются как взаимозаменяемые термины, и в большинстве случаев они означают одно и то же. Тем не менее есть тонкое историческое различие. «URL-кодирование» иногда может относиться к более старому формату application/x-www-form-urlencoded, используемому HTML-формами, который кодирует пробелы как + вместо %20.
Формат application/x-www-form-urlencoded был определён в спецификации HTML и появился раньше RFC 3986. У него немного другие правила: пробелы становятся +, а набор символов, которые не кодируются, слегка отличается. Современное употребление термина «URL-кодирование» практически всегда относится к процентному кодированию по RFC 3986, где пробелы — это %20.
На практике вам следует использовать процентное кодирование по RFC 3986 для всех контекстов URI (сегментов пути, параметров запроса в REST API, идентификаторов фрагментов). Формат application/x-www-form-urlencoded используйте только при работе с отправкой HTML-форм или когда этого требует конкретный API.
// Процентное кодирование по RFC 3986 (рекомендуется для URI)
encodeURIComponent('hello world') // "hello%20world"
// application/x-www-form-urlencoded (HTML-формы)
new URLSearchParams({q: 'hello world'}).toString() // "q=hello+world"
// Эквивалент на Python
from urllib.parse import quote, quote_plus
quote('hello world', safe='') # "hello%20world"
quote_plus('hello world') # "hello+world"