Инспектор процентного кодирования

Процентное кодирование — это не одно правило, а несколько, и они расходятся примерно в десятке символов. Эта страница пропускает один и тот же текст через encodeURIComponent, encodeURI и сериализацию application/x-www-form-urlencoded рядом, отмечает символы, где они расходятся, выводит строку на каждый символ с его байтами UTF-8 и классом RFC 3986 и показывает, сколько раз строка была закодирована. Сторона декодирования читает строку по строгому или нестрогому правилу и сообщает, какое экранирование сломано и где, вместо того чтобы молча вернуть неверный результат.
Вся обработка выполняется локально в вашем браузере — ваши данные никогда не загружаются ни на один сервер.

Не уверены, что такое процентное кодирование? Процентное кодирование простыми словами — что это такое, почему пробел становится %20 или +, и что значит, когда URL показывает %25.
Что это делает:
Как читать экранирования:
encodeURIComponent
encodeURI
форма (application/x-www-form-urlencoded)
Ctrl+Enter во вводе запускает активный режим.
Выберите режим и нажмите его кнопку действия. Кодировать и анализировать показывает три кодирования вашего текста плюс строку на каждый символ; Декодировать читает экранирования обратно и считает слои. Кнопка «Самопроверка» повторно прогоняет каждый задокументированный пример, каждый печатный символ ASCII и цепочки слоёв, записанные в файле данных этой страницы, и выводит здесь их количество.
Где кодировщики расходятся:только символы ниже обрабатываются тремя методами по-разному. Все остальные проходят через все три без изменений или экранируются всеми тремя одинаково.
Каждый символ, по одной строке:байты UTF-8, в которые превращается символ, класс, который ему назначает RFC 3986, и экранирование, которое записывает каждый контекст. Четыре колонки контекста — это наборы процентного кодирования WHATWG URL, которые браузер использует при сборке пути, запроса или фрагмента; затенённая строка — символ, который три кодировщика обрабатывают по-разному.
Точный набор, который эта страница экранирует для каждой части URL:выведен из самого кодировщика, поэтому эта таблица и код, заполняющий колонки выше, не могут разойтись. Каждый набор также экранирует управляющие символы C0 (от U+0000 до U+001F) и каждую кодовую точку выше U+007E, поэтому не-ASCII текст всегда экранируется в каждом контексте.
Эти наборы выведены из наборов процентного кодирования WHATWG и отличаются от них в двух намеренных местах, и оба делают ручную сборку безопасной: решётка экранируется внутри запроса, потому что сырая решётка начала бы фрагмент; а набор компонентов жёстко привязан к символам, которые экранирует encodeURIComponent (включая апостроф), тогда как набор форм привязан к тому, что экранирует URLSearchParams. Поэтому два столбца в таблице выше — это собственное поведение платформы, а не её переписанная реализация. Этот список также объясняет результат, который многих удивляет: знак процента экранируется encodeURIComponent и encodeURI, но он не входит ни в один из наборов контекстов WHATWG, поэтому колонки пути, запроса и фрагмента в таблице по символам его не трогают. Сырой знак процента в URL, собранном вручную, всё равно является риском, потому что стандарт ожидает, что вход был проверен заранее: %41 внутри запроса — это один A для многих серверных фреймворков, а строгое чтение этой страницы считает одиночный % сломанным экранированием. Самостоятельное экранирование как %25 — более безопасная привычка, и таблица по символам показывает оба написания рядом.
Сколько раз эта строка была закодирована:каждый раунд декодирования — это один слой. Значение, которое было закодировано дважды, начинается с%25где одно кодирование оставило бы%20, а значение, закодированное трижды, начинается с%2525.
Символы, которые URL может содержать как есть:RFC 3986 делит диапазон ASCII на три группы, и правила экранирования имеют смысл только относительно этого деления.
Зарезервированные символы не запрещены, а заняты: они что-то значат для разборщика URL. Правило, которое следует из списка, короткое — если символ зарезервирован и вы имеете в виду его как данные, экранируйте его; если он незарезервирован, экранировать его никогда не нужно.
Три метода и то, что каждый из них оставляет без изменений:
Списки — это точные наборы, которые каждый метод оставляет неэкранированными в диапазоне ASCII; самопроверка сверяет с ними каждый печатный символ ASCII, поэтому таблица не может разойтись с кодировщиками, которые она описывает.
Какой размер входа принимает эта страница и почему:процентное кодирование — это однопроходная обработка текста, поэтому, в отличие от конвертеров систем счисления на этом сайте, стоимость растёт прямолинейно, а не квадратично: нет длины, при которой сама арифметика становится проблемой. Ограничения ниже существуют потому, что таблица по символам выводится в документ, а таблица с миллионом строк — это то, что на самом деле останавливает браузер.
Что такое процентное кодирование?

Процентное кодирование — также называемое URL-кодированием, а в старых документах экранированием — это правило, которое позволяет URL нести символы, которые ему не разрешено содержать. Процентное экранирование состоит из трёх символов ASCII: знака процента и двух шестнадцатеричных цифр, обозначающих один байт. %20 — это байт 0x20, то есть пробел. %2F — это байт 0x2F, то есть слэш. Больше ничего не участвует: экранирование называет байт, а не символ.

Поскольку оно называет байты, текст сначала нужно превратить в байты, а кодировка байтов — UTF-8. Отсюда берутся длинные экранирования: один китайский иероглиф — это три байта UTF-8, а значит, три экранирования, а один эмодзи — четыре байта и двенадцать символов ASCII.

Версия на 30 секунд: пробел в значении URL — это %20, а в данных формы — +; # — это %23; буквальный знак процента — это %25. Три кодировщика на этой странице согласны насчёт букв, цифр и -_., и расходятся примерно в десятке знаков пунктуации, в том числе в тильде — именно поэтому encodeURIComponent и encodeURI дают разные строки для одного и того же текста, а неверный из двух может разбить значение на два параметра запроса.
Зачем URL вообще нужно это

URI определён как последовательность символов ASCII (RFC 3986, раздел 2.1), и внутри этого диапазона некоторые символы зарезервированы: это знаки пунктуации, которые придают URL структуру. Двоеточие и два слэша отделяют схему, знак @ отделяет информацию о пользователе от хоста, вопросительный знак начинает запрос, решётка начинает фрагмент, амперсанд разделяет параметры запроса, а знак процента вводит экранирование.

Это создаёт одну проблему и одну лазейку. Проблема: значение, которое само содержит один из этих символов — поле поиска с текстом «a&b», имя файла со слэшем, пароль с двоеточием, — иначе изменило бы структуру URL, в котором едет. Лазейка: сам знак процента, так что кодировщик может просто расширить собственный алфавит. % записывается как %25, а две шестнадцатеричные цифры после него — обычные незарезервированные символы.

Разобранные примеры

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

Три метода и когда каждый из них правильный

Короткая версия, которая спасает от большинства ошибок: всё, что идёт в одно значение параметра, проходит через encodeURIComponent (или через сериализатор форм, который делает то же с другим безопасным набором), а полный URL, который уже собран, проходит через encodeURI, если вообще нуждается в экранировании. Выбор неверного из этих двух — самая частая ошибка процентного кодирования в веб-коде.

Символы, которые может нести URL, полностью
Два знака плюс, два значения

Пробел — единственный символ, кодирование которого зависит от того, куда идёт текст, и причина этого — знак плюс.

Ловушка сидит в середине этой таблицы. В данных формы знак плюс означает пробел, поэтому значение, которое действительно содержит знак плюс — номер телефона вида +44 20 7946 0958, арифметическое выражение, адрес e-mail с меткой через плюс, — должно передаваться как %2B. Если оно передаётся как буквальный знак плюс, получатель прочитает его как пробел, и +44 придёт как « 44».

Закодировано дважды и как это распознать

Двойное кодирование случается, когда строка уже экранирована и затем экранируется снова. Знак процента первого экранирования — это такой же символ, как и любой другой, поэтому он становится %25, и нижний слой остаётся видимым текстом.

Распознать его — дело подсчёта: декодируйте строку один раз и посмотрите на результат. Если там всё ещё есть экранирования, похожие на намеренные — корректный %XX или знак плюс формы там, где ожидается пробел, — значит, есть ещё слой. Если там есть %25, за которым следуют две шестнадцатеричные цифры, следующий слой — точно экранирование.

Две практические заметки. Во-первых, декодирование дважды не безвредно: это хорошо известный способ заставить фильтр безопасности и код за ним видеть разные строки, так что фильтр, который работает до второго декодирования, фильтрует не тот текст. Во-вторых, подсчёт слоёв здесь — свойство строки, а не обещание об намерении: законное значение может быть глубиной в один слой, а сломанное — в два.

Ошибки, которые эта страница призвана ловить
  • Значение, закодированное через encodeURI. Разделители сохраняются, поэтому значение, содержащее & или =, на другой стороне распадается на несколько параметров. Сравните два столбца вверху этой страницы на входе a&b=c: колонка компонента экранирует оба символа, колонка encodeURI их сохраняет.
  • Буквальный знак плюс, отправленный как данные формы. Он придёт как пробел. Используйте %2B.
  • Значение, экранированное дважды «для безопасности». Это не безопаснее. Получатель декодирует один раз и получает строку, которая всё ещё содержит процентные экранирования, — та же ошибка, что и неверный набор символов, только на слой ниже.
  • Декодирование того, что никогда не кодировалось. Буквальный знак процента в тексте — 100% sure — не является экранированием. При строгом чтении это сломанное экранирование на позиции, которую сообщает страница; при нестрогом он сохраняется, поэтому нестрогое — умолчание здесь, а не наоборот.
  • Предположение, что экранирование должно быть в верхнем регистре. Декодирование считает %2f и %2F одним и тем же байтом, и оба допустимы. Верхний регистр — лишь канонический вид, который рекомендует RFC 3986 и который будут выдавать другие инструменты.
  • Предположение, что процентное кодирование делает текст безопасным. Оно делает текст однозначным внутри URL. Оно не останавливает инъекцию, не является шифрованием и не является санитайзером: экранирование должно быть последним шагом перед сборкой, для того контекста, куда попадает текст.
Быстрые ответы

“Знак плюс в URL — это пробел?”

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

“Почему моя ссылка показывает %25?”

Потому что что-то закодировало знак процента. Либо буквальный знак процента прошёл через кодировщик — 100% sure становится 100%25%20sure — либо строка, которая уже содержала экранирование, была закодирована второй раз. Декодируйте строку один раз и посмотрите, что вернулось: если результат всё ещё содержит %XX, есть ещё слой.

“Важно ли, записан ли слэш внутри значения запроса как %2F или /?”

Для разборщика URL — нет: оба доходят до сервера как слэш, и набор запроса WHATWG оставляет слэш в покое. Для совместимости — иногда да: многие серверные фреймворки выполняют собственное декодирование над сырой строкой запроса, и некоторые из них расходятся насчёт символов, которые никогда не экранировались. Когда значение собрано вручную, а не сериализатором, явное экранирование зарезервированных символов — более безопасная привычка.

“Почему колонка формы экранирует тильду, которую две другие оставляют в покое?”

Потому что безопасный набор кодировщика формы уже: только буквы, цифры и -_.*. Кодировщик форм WHATWG экранирует ~ ! ' ( ), хотя RFC 3986 относит ~ к незарезервированным. Оба написания декодируются в одно и то же значение; разница важна только при побайтовом сравнении строк.

“Могут ли те же символы появиться без экранирования в адресной строке браузера?”

Да, и это адресная строка, а не URL. Браузеры показывают читаемую форму URL и кодируют её при отправке, поэтому 中文 в ссылке становится %E4%B8%AD%E6%96%87 в сети. Если скопировать URL из адресной строки, вы можете получить любую из форм в зависимости от браузера.

“Каждое экранирование здесь выглядит корректно, но символ, который получается, — не тот, который я ожидал. Почему?”

За фразой «декодировалось неверно» скрываются два разных сбоя, и страница сообщает о них по-разному. Первый — сломанное экранирование: знак процента, за которым не следуют две шестнадцатеричные цифры, например завершающий 100% или %zz. Это проблема синтаксиса; строгое чтение её отвергает и сообщает, где она находится, а нестрогое чтение сохраняет её как буквальный текст и перечисляет. Второй — экранирование, байт которого вполне легален, но байты которого не складываются в корректный символ UTF-8, например %FF сам по себе или трёхбайтовый символ, обрезанный как %E4%B8. Это не проблема синтаксиса, поэтому оба чтения возвращают текст, заменяют непрочитываемые байты символом замены U+FFFD ("�") и выводят предупреждение об этом. Это предупреждение и есть полезная часть: текст, который приходит с символами замены, обычно означает, что отправитель использовал другую кодировку или что строка была экранирована один раз лишний. Процентное кодирование называет байты, и эта страница, как и стандарт URL, всегда читает эти байты как UTF-8.

“Чего эта страница не делает?”

Она не разбивает URL на схему, хост, путь и запрос — для этого есть Разборщик URL. Она не проверяет, безопасно ли открыть ссылку, ничего не обращается по сети и не угадывает кодировку байтов за экранированием: она всегда читает их как UTF-8, как того требует стандарт URL.

“Какой запасной вариант нужен декодированию и как узнать, что он верен?”

Нажмите Самопроверка. Она сверяет собственные кодировщики страницы с encodeURIComponent, encodeURI и URLSearchParams платформы для каждого печатного символа ASCII, заново прогоняет каждый пример в таблицах выше и перепроверяет записанные цепочки слоёв, затем выводит здесь число проверок.

Стандарты и область

Процентное кодирование описано в RFC 3986, раздел 2.1, определение зарезервированных и незарезервированных символов — в разделах 2.2 и 2.3, а правило регистра — в разделе 6.2.2.1. Наборы, которые браузер реально использует при сборке URL, описаны в стандарте URL WHATWG, который делит их по контексту: один набор для фрагмента, один для запроса, один для специального запроса, один для пути, один для информации о пользователе и более узкий, используемый для отдельного компонента. Отправка формы, включая знак плюс, описана в стандарте HTML в разделе application/x-www-form-urlencoded. Две функции encodeURI и encodeURIComponent входят в сам ECMAScript.

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

Текст страницы написан для этого сайта. Каждое значение в таблицах выше создаётся собственным кодом страницы при загрузке и проверено по реализациям платформы в инструментах тестирования этого сайта; самопроверка повторяет это сравнение в вашем браузере.