Кодирование и декодирование 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 без экранирования, принятого уже там.
Последняя проверка . Заметили, что что-то устарело? Напишите нам.
