Кодирование и декодирование URL

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

Три правила, и выбрать не то — это и есть ошибка

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

ПравилоЭкранирует / ? & = #Пробел становитсяПрименять для
encodeURIComponentДа%20Значения, попадающего в URL
encodeURIНет%20Приведения в порядок целого URL
Кодирование формДа+Того, что отправляет HTML-форма

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

Ошибка, которую это предотвращает

Пусть поисковое значение — coffee & cake. Закодированное как компонент, оно превращается в coffee%20%26%20cake, и URL получается такой:

/search?q=coffee%20%26%20cake

Один параметр, значение цело. Закодированное через encodeURI, амперсанд остаётся собой:

/search?q=coffee%20&%20cake

Теперь сервер видит два параметра: q равен «coffee », и есть второй, пустой, по имени « cake». Никакой ошибки. Поиск просто тихо возвращает не то, и это как раз тот дефект, который переживает ревью, потому что URL выглядит нормально.

Плюс, который не плюс

Второй надёжный источник путаницы. В данных, закодированных как форма, + означает пробел, а настоящий плюс приходится писать %2B. Поэтому раскодирование C%2B%2B по правилу формы даёт C++, а раскодирование C++ даёт C — и два пробела, а название языка исчезло.

Больнее всего это бьёт по телефонным номерам. +44 20 7946 0958, отправленный через форму и раскодированный не тем правилом, превращается в 44 20 7946 0958, теряя код страны, — и нигде никакой ошибки.

Что делать вместо ручного кодирования

В JavaScript стройте строки запроса через URLSearchParams, а не склейкой. Он применяет к каждому значению нужное правило и справляется с повторяющимися ключами:

const url = new URL('https://example.com/search');
url.searchParams.set('q', 'coffee & cake');
url.searchParams.set('page', '2');
// https://example.com/search?q=coffee+%26+cake&page=2

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

Как читать закодированный URL

Несколько последовательностей стоит узнавать в лицо — они попадаются в логах постоянно:

ПоследовательностьСимволПочему это важно
%20пробелСамая частая с большим отрывом
%2F/Закодированная косая черта в пути часто означает попытку обхода каталогов
%3A:Обычна в закодированных URL внутри параметров перенаправления
%25%Двойное кодирование проявляется как %2520
%00нулевой байтПочти никогда не бывает законным

Особенно стоит знать %2520: это %20, закодированный второй раз. Обычно это значит, что значение прошло через два слоя, каждый из которых его закодировал, и служит подписью цепочки перенаправлений или прокси, делающего что-то не то.

Смежное

Для кодирования байтов, а не экранирования текста, Base64 — это другая задача. Если раскодированное значение окажется JSON, его прочтёт форматтер, а если это токен — декодер JWT.

Вопросы о кодировании

Чем encodeURI отличается от encodeURIComponent?

encodeURIComponent экранирует символы, имеющие структурное значение в URL — / ? & = # : и другие, — поскольку исходит из того, что вы кодируете значение, которое встанет внутрь адреса. encodeURI их не трогает, потому что предполагает, что ему передали целый URL, который нужно лишь привести в порядок. Кодировать параметр запроса через encodeURI — распространённая ошибка: символ & внутри значения уцелеет и молча разобьёт ваш параметр надвое.

Почему пробел иногда %20, а иногда +?

Оба варианта верны, каждый в своём контексте. %20 — это обычное процентное кодирование пробела, работающее в любой части URL. Соглашение с + пришло из application/x-www-form-urlencoded, формата, в котором отправляются HTML-формы: там + означает пробел, а настоящий плюс приходится писать как %2B. Значит, пробел в пути — это %20, а пробел в отправленных данных формы — это +. Расшифровка данных формы не по тому правилу превращает каждый плюс в тексте пользователя в пробел.

Я получил ошибку URI malformed. Отчего она?

От знака процента, за которым не идут две шестнадцатеричные цифры. Обычно это буквальный % в тексте, который никто не закодировал: промокод вроде «50% off» даёт именно такой сбой, потому что %20 читается как escape-последовательность, а %off недопустим. Буквальный процент надо писать как %25. Вторая причина — двойное декодирование: если дважды раскодировать то, что кодировали один раз, рано или поздно наткнётесь на % , который должен был остаться на месте.

Кодировать весь URL или только значения?

Практически всегда только значения. Собирайте адрес по частям и кодируйте каждый параметр при вставке — в JavaScript это делает URLSearchParams, и он надёжнее ручного кодирования. Кодировать URL целиком имеет смысл лишь тогда, когда вам передали уже испорченный адрес и вы его латаете.

Делает ли кодирование URL его безопасным?

Нет. Процентное кодирование касается синтаксиса, а не безопасности: оно лишь гарантирует, что значение переживёт помещение в URL, не изменив его структуру. Куда ведёт адрес, оно никак не меняет и защитой от инъекций не является: значение, корректно закодированное для URL, остаётся опасным, если потом его подставят в HTML или SQL без экранирования, принятого уже там.

Последняя проверка . Заметили, что что-то устарело? Напишите нам.