Инструменты разработчика · Кодер и декодер URL
WHATWG URL Стандарт против RFC 3986: почему браузеры и библиотеки расходятся во мнениях
· Фон
URL-кодировка стандарты инструменты разработчика
Есть два живых определения URL, и они намеренно расходятся. В этом посте объясняется, почему WHATWG написал свой собственный стандарт, в котором они различаются по кодированию и синтаксическому анализу, и какому из них следует ваш код.
URL, который строгая библиотека отвергает и браузер с радостью загружает — одна строка, два вердикта
Строка с обратной косой чертой в JavaScript может быть легко интерпретирована вашим браузером как часть пути URL. Та же самая строка достигает серверной части библиотеки Python, но она отказывается анализировать ее, поскольку обратные косые черты не допускаются. Один URL, два разных результата. В этом нет ничего плохого: они следуют разным стандартам. Стандарт WHATWG описывает, что браузеры на самом деле делают с реальными URL-адресами, включая то, как они обрабатывают неверный ввод. RFC 3986 определяет формальную грамматику, которой в идеале должны соответствовать URL-адреса. Многие серверные библиотеки, построенные на RFC 3986, строго соблюдают эту грамматику и отвергают все, что находится за ее пределами.
Это расхождение имеет значение при перемещении данных между средами. Браузеры принимают URL и могут не пройти проверку в серверном инструменте. Понимание того, какой стандарт реализует ваш код, предотвращает отладку фантомных проблем: URL-адреса работают нормально в одном месте, но таинственным образом выходят из строя где-то без видимой причины.
Почему WHATWG начался сначала — описание того, что браузеры на самом деле делают с неверным вводом, а не того, что действительно правильно
Рабочая группа WHATWG была сформирована в 2004 для стандартизации того, как браузеры фактически обрабатывают URL-адреса на практике, а не для определения более строгих формальных правил, которым браузеры не будут следовать. RFC 2396 описывает формальную спецификацию грамматики, но на практике браузеры никогда не следовали ей точно. Реальные браузеры разработали практические правила для допуска пробелов, обработки экранированных символов и восстановления после неправильного ввода, чего RFC не ожидал или не ожидал.
RFC 3986 появился в 2005 с формальной грамматикой для правильно сформированных URL-адресов и строгими требованиями. Браузеры реализуют WHATWG; серверные библиотеки часто реализуют RFC 3986.
Наборы кодирования в сравнении с зарезервированными символами — как списки компонентов стандарта URL соотносятся с категориями RFC 3986
RFC 3986 делит символы на три категории: зарезервированные, незарезервированные и все остальное, что необходимо закодировать. Зарезервированные символы, такие как двоеточие, косая черта, вопросительный знак и решетка, имеют структурное значение в URL-адресах. Незарезервированные символы — это буквы, цифры, дефис, подчеркивание, точка и тильда; они всегда безопасны. Все остальное кодируется в процентах в байтах. Стандарт предусматривает одно четкое правило: знайте, к какой категории принадлежит ваш персонаж.
Стандарт WHATWG URL использует компонентный подход. Он определяет разные правила кодирования для схемы, авторитета, пути, запроса и фрагмента отдельно вместо использования глобальных категорий. Амперсанд может быть закодирован в пути, но оставлен отдельно в строке запроса. Пространство всегда закодировано, но точное представление зависит от контекста. Этот покомпонентный дизайн гораздо лучше соответствует поведению браузера, но требует знания того, какую часть URL вы кодируете.
Допуск ошибок: пробелы, обратные косые черты и табуляции — один стандартный вход отклоняет, а другой исправляет.
В соответствии с обоими стандартами пробелы должны стать %20, но браузеры автоматически преобразуют литеральные пробелы. Обратная косая черта запрещена обоими стандартами, однако некоторые браузеры рассматривают ее как разделитель пути. Табуляция, символы новой строки и управляющие символы запрещены. WHATWG определяет мягкое поведение анализатора: преобразуйте или игнорируйте их.
Символы, не относящиеся к ASCII, такие как é или 中, должны быть закодированы в процентах с использованием кодировки UTF-8. RFC 3986 фактически не определяет сам шаг кодирования символов; он предполагает, что байты существуют, но не говорит, как получить их из текста. Стандарт WHATWG явно требует UTF-8: сначала преобразуйте строку в UTF-8 байты, а затем закодируйте их в процентах. Оба стандарта достигают одного и того же результата кодирования, но исходят из разных исходных предположений и не содержат явного описания одних и тех же вещей.
Рабочий пример: анализ URL с обратной косой чертой и пробелом в обеих моделях — сравнение результатов
Возьмем пример строки «Поиск https://example.com/café\». Браузер встречает обратную косую черту и воспринимает ее как символ пути; он видит пробел и кодирует его в %20, создавая что-то вроде https://example.com/café%5C%20search. Анализатор RFC 3986 немедленно отклоняет весь URL, поскольку обратная косая черта запрещена, а пробелы запрещены. Браузер продолжает анализ; строгий парсер полностью останавливается. Попробуйте другой пример: «https://user@example.com:80/path?q=a&b=c". Оба стандарта четко определяют информацию о пользователе, хост, порт, путь и запрос. Они полностью согласны в этой структурированной URL. Разногласия возникают только в случае необычных или неправильно сформированных входных данных.
Откройте кодировщик и декодер URL и сравните режим RFC 3986 с поведением браузера. Вставьте строку с пробелами, обратными косыми чертами или другими крайними случаями. Инструмент показывает, как каждый стандарт по-разному преобразует одни и те же входные данные. Сразу видно, кто из них строже и что делает каждый.
Какой из них использует ваша среда — браузеры и Node соответствуют стандарту URL; многие серверные библиотеки следуют RFC, описанному в общих чертах.
В браузерах JavaScript по умолчанию использует стандарт WHATWG URL. URL API в точности реализует это. Node.js также использует WHATWG. Библиотеки Python обычно реализуют RFC 3986; urllib внимательно следует за ним. Библиотеки Java различаются; java.net.URL стремится к RFC 3986. URL-адрес Rust следует за WHATWG. Сеть Go /url находится под влиянием WHATWG. Это общая закономерность, а не абсолютное правило.
Когда вы создаете URL-адреса программным способом и они перемещаются между браузером и серверной частью, выберите один стандарт и придерживайтесь его. Используйте URL API браузера для WHATWG. Если ваша серверная библиотека более строгая, это не противоречие, а выбор дизайна.
Что здесь не распространяется — анализ имени хоста, литералы IPv6 и обработка IDNA.
Анализ имени хоста включает в себя IDNA, punycode и правила регистратора, которые выходят за рамки полного анализа URL. Адреса IPv6, специальные схемы, такие как mailto: или data:, и пустые компоненты — это отдельные темы, полностью отличающиеся от процентного кодирования. Ограничения на длину домена и действительность имени хоста зависят от регистратора и не имеют отношения к данному обсуждению. Также исключены: относительные ссылки и правила синтаксического анализа, специфичные для схемы. Этот пост посвящен только различиям в кодировании и анализе.
В этом обсуждении основное внимание уделяется различиям в кодировании и синтаксическом анализе, которые отличают эти стандарты. Исключение правил имени хоста, правил DNS и поведения, специфичного для схемы, предотвращает путаницу в отношении правил процентного кодирования.
Вывод: один и тот же URL действителен в одном мире и является ошибкой в другом — как кодировщик и декодер URL дают вам простую кодировку RFC 3986, чтобы вы могли видеть, что браузер нормализовал
Одна и та же строка URL может быть допустима в соответствии с одним стандартом и недействительна в соответствии с другим. Оба правы в рамках своих собственных целей дизайна. При программном кодировании компонентов URL используйте инструмент, подходящий для вашей среды. WHATWG описывает, что на самом деле делают браузеры; RFC 3986 определяет формальную грамматику. Кодировщик и декодер URL отображает RFC 3986 правила наряду с поведением браузера, поэтому вы можете увидеть точные различия и выбрать то, что соответствует вашей ситуации.
Проблемы возникают чаще всего, когда URL-адреса пересекают границы между браузером и серверной частью. Понимание этой разницы означает, что нужно обращаться с этим пересечением намеренно, а не случайно или по ошибке.