Как работает URL-кодирование
В URL разрешён лишь небольшой набор символов ASCII, и у некоторых из них есть своя роль: / разделяет путь, ? начинает запрос, & и = делят его на параметры, # начинает фрагмент. Любой другой символ или один из этих, если он используется как обычные данные, записывается как % и две шестнадцатеричные цифры на каждый его байт в UTF-8. Это называется процентным кодированием (RFC 3986).
пробел → %20 (или + в данных формы) & → %26 / → %2F é → C3 A9 → %C3%A9 € → E2 82 AC → %E2%82%AC
Какие символы кодировать, зависит от того, что вы кодируете. В отдельном значении, например поисковом запросе, нужно кодировать и / ? & = #, иначе URL, в который оно попадёт, сломается: это encodeURIComponent в JavaScript и urlencode в PHP. В целом URL они должны остаться, иначе он перестанет быть ссылкой: это encodeURI. HTML-формы отправляют пробелы как + вместо %20 (application/x-www-form-urlencoded), поэтому + в строке запроса обычно означает пробел.
Декодирование выполняет обратное: каждый %XX становится байтом, а байты читаются как UTF-8. Встроенная в браузер decodeURIComponent при ошибке лишь сообщает «URI malformed». Эта страница называет последовательность и указывает на неё: это % без двух шестнадцатеричных цифр после него или байты, не являющиеся корректным UTF-8, что обычно означает, что текст был закодирован как Latin-1.
Пример
Поисковый запрос «vps hosting/€5» как одно значение превращается в vps%20hosting%2F%E2%82%AC5: пробел — это %20, косая черта — %2F, а знак евро, занимающий в UTF-8 три байта, становится %E2%82%AC. С включённым «Писать пробелы как +» для данных формы получится vps+hosting%2F%E2%82%AC5. Декодирование r%C3%A9sum%C3%A9 даёт résumé, а декодирование caf%E9 завершается ошибкой на %E9, потому что это é в Latin-1, а не в UTF-8.
Вопросы
Что такое URL-кодирование?
URL-кодирование, или процентное кодирование (percent-encoding), записывает символы, которые нельзя использовать в URL или которые имеют там особый смысл, как % и две шестнадцатеричные цифры на каждый байт. Пробел превращается в %20, & — в %26, а é — в %C3%A9. Так любой текст можно безопасно передать внутри ссылки или строки запроса.
Чем отличаются encodeURI и encodeURIComponent?
encodeURI предназначена для всего адреса: она оставляет символы, у которых в URL есть своя роль, например : / ? # & =, чтобы ссылка продолжала работать. encodeURIComponent предназначена для одной части внутри URL, например значения параметра: она кодирует и эти символы, поэтому & в значении не может начать новый параметр. Если сомневаетесь, кодируйте каждое значение через encodeURIComponent, а затем соединяйте их.
Что писать вместо пробела: %20 или +?
Оба варианта верны, но в разных местах. В пути URL пробел должен быть %20. В строке запроса, которую отправляет HTML-форма (application/x-www-form-urlencoded), пробел записывается как +, а настоящий плюс — как %2B. Большинство серверов в строке запроса понимают оба. При декодировании данных формы включите «Читать + как пробел».
Почему при декодировании я получаю «URI malformed»?
Либо после % нет двух шестнадцатеричных цифр, например в обычном «100%» или «%G1», либо байты %XX не являются корректным UTF-8: часто потому, что текст закодирован в Latin-1 (é как %E9) или многобайтовый символ обрезан. Эта страница показывает, какая последовательность неверна и где она находится, чтобы вы могли это исправить.
Как декодировать URL в командной строке или в коде?
В JavaScript: decodeURIComponent(s), после s.replace(/\+/g, ' ') для данных формы. В Python: urllib.parse.unquote(s) или unquote_plus(s) для данных формы. В PHP: rawurldecode($s) или urldecode($s), которая заодно превращает + в пробел. В командной строке с установленным Python: python3 -c "import sys, urllib.parse as u; print(u.unquote(sys.argv[1]))" 'r%C3%A9sum%C3%A9'.