Кодирование

URL-кодирование (процентное кодирование): почему текст превращается в %20 и %D0%BF

Скопируйте ссылку с русским поисковым запросом — и получите строку вроде %D0%BF%D1%80%D0%B8%D0%B2%D0%B5%D1%82. Это URL-кодирование. В руководстве объясняются его правила и проблемы, которые оно создаёт в веб-разработке, маркетинговых ссылках и интеграциях с API.

В URL допустимы не все символы

Синтаксис URL определён в RFC 3986. Без изменений в URL могут стоять только латинские буквы, цифры и четыре символа: дефис (-), точка (.), подчёркивание (_) и тильда (~). Их называют незарезервированными символами.

Символы :, /, ?, #, & и = зарезервированы: они задают структуру URL. ? начинает строку запроса, & разделяет параметры, а # обозначает фрагмент. Если сам поисковый запрос содержит & или #, вставка без кодирования меняет смысл всего URL.

Как работает процентное кодирование

Символы, которые нельзя использовать напрямую, разбиваются на байты, и каждый байт записывается как % и две шестнадцатеричные цифры. Пробел (байт 0x20) превращается в %20, амперсанд (0x26) — в %26, знак равенства (0x3D) — в %3D.

Текст вне ASCII сначала переводится в байты UTF-8, а затем кодируется тем же способом. Корейский слог 안 занимает в UTF-8 три байта (EC 95 88) и поэтому становится %EC%95%88 — один символ превращается в девять. Русская буква «п» из двух байтов становится %D0%BF.

안녕 하세요?  →  %EC%95%88%EB%85%95%20%ED%95%98%EC%84%B8%EC%9A%94%3F
a b&c=d      →  a%20b%26c%3Dd

Декодирование выполняет обратный процесс: %EC%95%88 снова становится байтами EC 95 88, которые интерпретируются как UTF-8. Если сервер интерпретирует эти байты в устаревшей кодировке, например Windows-1251, получаются «кракозябры» — частая причина испорченных поисковых запросов на старых сайтах.

Пробел — это %20 или +?

Больше всего путаницы с пробелами. По RFC 3986 пробел записывается как %20. Но формат application/x-www-form-urlencoded, который используют HTML-формы, пишет пробелы в строке запроса как +. Поэтому один и тот же текст a b может выглядеть как a%20b в пути и как a+b в запросе формы.

Проблемы начинаются, когда нужен настоящий знак плюс. Телефон вроде +7 или выражение 1+1, вставленные в запрос без кодирования, могут быть прочитаны как пробел. Кодируйте такие значения как %2B. Библиотеки ведут себя по-разному: encodeURIComponent в JavaScript выдаёт %20, а некоторые серверные библиотеки — +, поэтому убедитесь, что обе стороны используют одно соглашение.

Кодируйте части, а не весь URL

Никогда не кодируйте URL целиком за один раз. Если закодировать https://example.com/search?q=..., символы : и / превратятся в %3A и %2F, и результат перестанет работать как адрес. Кодируйте только те части, которые являются данными, например значения параметров.

В JavaScript это отражено двумя функциями. encodeURI не трогает структурные символы, а encodeURIComponent кодирует ещё и /, ?, & и =. Для значений параметров запроса безопасный выбор — encodeURIComponent.

Двойное кодирование: насторожитесь, увидев %2520

Если повторно закодировать уже закодированную строку, % превратится в %25. Если пробел выглядит как %2520, а не %20, его закодировали дважды. Так часто бывает с трекинговыми ссылками, проходящими через несколько редиректов, или с адресами возврата после входа, которые передают другой URL в параметре.

Двойное декодирование не менее вредно: настоящий %25 в данных превращается в %, и значение меняется. Правило простое: кодируйте один раз — в последний момент, когда значение попадает в URL, и декодируйте один раз — в первый момент, когда вы его читаете.

Практический чек-лист

Попробуйте значения в URL-кодировщике/декодере AIBIZAP, чтобы точно увидеть, какие символы и как меняются.

  • Кодируйте названия кампаний в UTM-ссылках, если в них есть пробелы или кириллица.
  • Кодируйте на уровне частей значения для API: поисковые запросы, адреса почты (с @) и метки времени (с :).
  • Если ссылка сломалась, ищите %25, чтобы выявить двойное кодирование.
  • Если в логах сервера «кракозябры», проверьте, что значения декодировались и интерпретировались как UTF-8.
  • Если значение Base64 нужно вставить в URL, используйте Base64URL или закодируйте результат процентным кодированием.

Попробуйте с этими инструментами

Другие руководства

Все руководства