Кодировщик / декодировщик URL

Кодируйте и декодируйте URL, выбирайте область «компонент» или «весь URL» и разбирайте адрес на части.

Для отдельного значения, например одного параметра запроса. Экранирует и / ? : @ & = +, потому что там это данные, а не структура.

Ввод
Вывод

Здесь появится вывод

Зачем нужно процентное кодирование

URL может содержать лишь небольшой набор символов: буквы A–Z и a–z, цифры, горстку знаков вроде - . _ ~ и набор зарезервированных символов, которые задают адресу форму, — / ? # & = : @ и ещё несколько. Всё остальное, от пробела до слова на иврите или эмодзи, приходится представлять косвенно. Процентное кодирование и есть это представление: символ переводится в свои байты UTF-8, и каждый байт записывается знаком процента и двумя шестнадцатеричными цифрами. Пробел становится %20, а א становится %D7%90.

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

Компонент или весь URL — различие, которое ломает ссылки

Это самый частый источник ошибок с URL, и именно поэтому инструмент просит выбрать область, а не угадывает сам.

  • Область компонента экранирует и разделители: / ? : @ & = + $ , и #. Используйте её для одной порции данных — поискового запроса, адреса перенаправления, токена, — которая помещается внутрь более крупного URL.
  • Область «весь URL» оставляет эти разделители нетронутыми и экранирует только то, что действительно недопустимо: пробелы и не-ASCII текст. Используйте её, когда у вас уже есть готовый, правильно построенный адрес, который нужно лишь привести в порядок.

Ошибка в любую сторону что-нибудь ломает. Закодируйте целый URL в области компонента — и каждая косая черта и вопросительный знак превратятся в %2F и %3F, дав одну непригодную строку. Закодируйте отдельное значение в области всего URL — и амперсанд внутри него уцелеет как разделитель: поиск «кошки & собаки» тихо превратится в два параметра, а вторая половина значения исчезнет.

// Значение содержит разделитель, поэтому его нужно экранировать:
const q = 'cats & dogs'
`/search?q=${encodeURIComponent(q)}`  // /search?q=cats%20%26%20dogs
`/search?q=${encodeURI(q)}`           // /search?q=cats%20&%20dogs  ✗

// А лучше — пусть платформа соберёт адрес сама
const url = new URL('https://example.com/search')
url.searchParams.set('q', 'cats & dogs')

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

Почему пробел то +, то %20

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

Ловушка в том, что decodeURIComponent реализует только первое правило. Передайте ему hello+world — и получите обратно hello+world вместе с плюсом: ни ошибки, ни предупреждения, просто едва заметно неверное значение. Поэтому инструмент предлагает опцию «считать + пробелом» и указывает на неё, когда во входных данных есть плюс, а опция выключена.

Обратите внимание: URLSearchParams, на котором держится таблица разбора под инструментом, следует правилам форм и как раз превращает + в пробел. Так что одна и та же строка запроса может декодироваться по-разному в зависимости от выбранного API — намеренно и правильно в обоих случаях.

Одно следствие стоит запомнить: если плюс действительно часть ваших данных — номер телефона, выражение фильтра, — его нужно кодировать как %2B. Буквальный + в строке запроса в лучшем случае двусмыслен, и большинство разборщиков прочтут его как пробел.

Как читать URL

Вставьте абсолютный адрес, и инструмент разложит его на части. Названия стоит знать: сообщения об ошибках и документация исходят из того, что этот словарь вам знаком.

  • Схема — https, mailto, ftp. Всё до двоеточия и то, что определяет, как трактовать остальное.
  • Данные пользователя — необязательные user:password перед узлом. По-прежнему допустимы и по-прежнему плохая идея: они едут с каждым запросом и оседают в журналах, истории браузера и заголовках referrer.
  • Узел — доменное имя или IP-адрес. Всё, что идёт после него, обрабатывает этот сервер, а не сеть.
  • Порт — обычно отсутствует, потому что 443 для https и 80 для http подразумеваются.
  • Источник — схема, узел и порт вместе. Это та единица, на которой держится безопасность браузера: политика одного источника, CORS и область действия куки сравнивают источники, а не пути.
  • Путь — часть после узла и до любого ? или #.
  • Строка запроса — пары «ключ/значение» после ?, разделённые &.
  • Фрагмент — всё после #. Единственная часть, которая никогда не отправляется на сервер: её целиком обрабатывает браузер.

Эта деталь про фрагмент важнее, чем кажется. Поскольку он не покидает браузер, всё, что вы поместите после #, невидимо для серверных журналов — потому некоторые одностраничные приложения исторически использовали его для маршрутизации, и потому там не стоит искать причину запроса, который так и не дошёл.

И последнее предостережение о том, чего кодирование не делает. Процентное кодирование делает текст безопасным для перевозки внутри URL; это не санитизация и не мера безопасности. Закодированное значение не становится безопасным для подстановки в HTML, SQL или команду оболочки, а декодирование недоверенного ввода может открыть символы — разделители пути, нулевые байты, — которые скрывала закодированная форма. Проверяйте, чем значение является, отдельно от кодирования того, как оно путешествует.

Набранный адрес не всегда тот, что уходит

Прежде чем адрес куда-либо отправится, он нормализуется, и переписывание происходит молча. Инструмент показывает результат всякий раз, когда он отличается от ввода, потому что именно эта строка — а не набранная — доходит до сервера и попадает в журнал:

HTTPS://Example.COM:443\a\b     набрано
https://example.com/a/b         отправлено

Там сработали четыре разных правила. Схема и узел переходят в нижний регистр, поскольку ни то ни другое не различает регистр. Порт исчезает, потому что 443 — значение по умолчанию для https. Обратные слеши становятся прямыми: правило совместимости, которое застаёт врасплох тех, кто пишет пути в стиле Windows. А пустой путь стал бы одиночным слешем. Ничто из этого не ошибка, но если вы сравниваете два адреса или сверяете один со списком разрешённых, сравнивать нужно нормализованные формы, иначе ответ будет неверным.

Имя узла с символами вне ASCII тоже переписывается — в ASCII-форму, начинающуюся с xn--, которая называется punycode. Инструмент показывает оба направления: читаемое имя и то, что действительно едет. На это стоит посмотреть, а не пролистать, потому что два разных юникодных имени могут выглядеть на экране одинаково — латинская «a» и кириллическая «а» это разные символы — и при этом давать совершенно разный punycode. Сравнение ASCII-форм — единственный надёжный способ различить такую пару.

Одно не нормализуется никогда: повторяющийся параметр запроса. Написать ?tag=a&tag=b совершенно законно, а стандарт не говорит, что это значит, поэтому каждый стек выбрал свой ответ. PHP оставляет последнее значение, Express собирает их в массив, многие фреймворки и URLSearchParams.get берут первое. Инструмент помечает повторяющиеся ключи, а не перечисляет их дважды без комментария, потому что возникающую ошибку — значение работает в одном сервисе и пропадает в другом — по-настоящему трудно заметить при чтении.

Частые вопросы

Чем отличаются область компонента и область всего URL?
Область компонента экранирует и разделители / ? : @ & = +, что правильно для отдельного значения, помещаемого внутрь URL. Область всего URL оставляет их нетронутыми, потому что там они разделяют части адреса. Применение области компонента ко всему URL превращает каждую косую черту в %2F и даёт непригодную строку.
Почему в декодированном тексте всё ещё есть +?
Потому что decodeURIComponent следует RFC 3986, где + — просто плюс. Отправка форм и большинство строк запроса используют кодирование форм, где + означает пробел. Включите «считать + пробелом», если текст пришёл оттуда.
Как закодировать плюс, который действительно плюс?
Записать его как %2B. Буквальный + в строке запроса большинство разборщиков прочтут как пробел, поэтому любой плюс, который на самом деле часть ваших данных — например, в номере телефона, — нужно экранировать.
Кодировать весь URL или только части?
Только части, и желательно не вручную: собирайте адрес через URL и URLSearchParams, которые применяют правильное кодирование к каждому компоненту. Кодирование уже собранного URL задним числом — источник большинства ошибок двойного кодирования.
Почему разбор показывает не тот адрес, который я вставил?
Потому что показанный и уходит. Адрес нормализуется перед использованием: схема и узел переходят в нижний регистр, порт по умолчанию убирается, обратные слеши становятся прямыми, пустой путь становится слешем, а узел вне ASCII становится punycode. Если вы сравниваете адреса или сверяете их со списком разрешённых, сравнивайте именно эти нормализованные формы, а не исходный текст.
Почему для example.com/path нет разбора?
Потому что это не абсолютный адрес — у него нет схемы, а значит, нет и узла, который можно определить. Инструмент не угадывает её за вас: example.com:8080 уже допустимый абсолютный адрес, схема которого example.com, а путь 8080, поэтому молчаливое добавление https:// способно дать разбор, который выглядит разумно и при этом неверен. Добавьте схему сами, и части появятся.
Что такое двойное кодирование?
Кодирование значения, которое уже было закодировано, так что %20 превращается в %2520 — экранируется сам знак процента. Обычно это проявляется как буквальные последовательности %20, видимые на странице. Декодируйте один раз и посмотрите, остались ли escape-последовательности; если да, значит закодировали дважды.
Делает ли кодирование значение безопасным?
Нет. Процентное кодирование — про перевозку, а не про безопасность. Закодированное значение остаётся тем, чем было, и требует той же проверки и того же уместного для контекста экранирования, прежде чем попадёт в HTML, SQL или оболочку.
Почему фрагмент не отправляется на сервер?
Так задумано: всё после # обрабатывает только браузер, и в запросе оно не появляется никогда. Поэтому его не видно в серверных журналах и поэтому исторически он использовался для маршрутизации на стороне клиента.
Отправляется ли куда-нибудь то, что я вставляю?
Нет. Кодирование, декодирование и разбор URL используют встроенные функции самого браузера и выполняются целиком на вашем устройстве. Ничто из вставленного его не покидает.