Інспектор відсоткового кодування

Відсоткове кодування — не одне правило, а кілька, і вони розходяться щодо десятка символів. Ця сторінка пропускає той самий текст через encodeURIComponent, encodeURI і серіалізацію application/x-www-form-urlencoded поруч, позначає символи, де вони розходяться, виводить рядок на кожен символ з його байтами UTF-8 і класом RFC 3986 і показує, скільки разів рядок було закодовано. Декодувальна частина читає рядок за правилом строгим або поблажливим і каже, яке екранування зламане і де, замість того щоб тихо повернути неправильний результат.
Уся обробка відбувається локально у вашому браузері — ваші дані ніколи не вивантажуються на жоден сервер.

Не впевнені, що таке відсоткове кодування? Відсоткове кодування простими словами — що це таке, чому пробіл стає %20 або +, і що означає, коли URL показує %25.
Що це робить:
Як читати екранування:
encodeURIComponent
encodeURI
форма (application/x-www-form-urlencoded)
Ctrl+Enter у полі введення запускає активний режим.
Оберіть режим і натисніть його кнопку дії. Кодувати і перевірити показує три кодування вашого тексту плюс рядок на кожен символ; Декодувати повертає екранування назад і рахує шари. Кнопка самоперевірки повторно запускає кожен задокументований зразок, кожен друкований символ ASCII і ланцюжки шарів, записані у файлі даних цієї сторінки, і показує тут кількість.
Де кодувальники розходяться:лише символи нижче обробляються по-різному трьома методами. Усе інше проходить через усі три без змін або однаково екранується всіма трьома.
Кожен символ, по одному рядку:байти UTF-8, у які перетворюється символ, клас, який RFC 3986 йому надає, і екранування, яке записує кожен контекст. Чотири стовпці контекстів — це набори відсоткового кодування WHATWG, якими браузер користується під час побудови шляху, запиту чи фрагмента; затінений рядок — це символ, який три кодувальники обробляють по-різному.
Точний набір, який ця сторінка екранує для кожної частини URL:виведено з самого кодувальника, тож ця таблиця і код, що заповнює стовпці вище, не можуть розійтися. Кожен набір також екранує керуючі символи C0 (U+0000 до U+001F) і кожну кодову точку вище U+007E, тож текст поза ASCII завжди екранується в кожному контексті.
Ці набори походять від наборів відсоткового кодування WHATWG і відрізняються від них у двох навмисних місцях, які обидва роблять складання вручну безпечним: хеш екранується всередині запиту, бо сирий почав би фрагмент; а набір компонента зафіксовано точно на символах, які екранує encodeURIComponent, включно з апострофом, тоді як набір форми зафіксовано на тому, що екранує URLSearchParams. Два стовпці в таблиці вище — це тому поведінка самої платформи, а не її переписана реалізація. Цей список також пояснює результат, який людей дивує: знак відсотка екранує encodeURIComponent і encodeURI, але він не входить до жодного з наборів контекстів WHATWG, тож стовпці шляху, запиту і фрагмента в таблиці по символах його не чіпають. Сирий знак відсотка в URL, який ви склали вручну, все одно є ризиком, бо стандарт очікує, що вхід вже перевірено: %41 у запиті — це один A для багатьох серверних фреймворків, а строге читання цієї сторінки трактує самотній % як зламане екранування. Екранувати його самостійно як %25 — безпечніша звичка, а таблиця по символах показує обидва написання поруч.
Скільки разів цей рядок було закодовано:кожен раунд декодування — це один шар. Значення, закодоване двічі, починається з%25де одне кодування залишило б%20, а значення, закодоване тричі, починається з%2525.
Символи, які URL може містити як є:RFC 3986 ділить діапазон ASCII на три групи, і правила екранування мають сенс лише відносно цього поділу.
Зарезервовані символи не заборонені, вони зайняті: вони щось означають для розбирача URL. Правило, що випливає зі списку, коротке — якщо символ зарезервовано і ви маєте його на увазі як дані, екрануйте його; якщо він незарезервований, екранувати його ніколи не потрібно.
Три методи і що кожен із них залишає без змін:
Списки — це точні набори, які кожен метод залишає без екранування в діапазоні ASCII; самоперевірка перевіряє кожен друкований символ ASCII проти них, тож таблиця не може розійтися з кодувальниками, які описує.
Яким великим може бути вхід на цій сторінці і чому:відсоткове кодування — це один прохід по тексту, тож на відміну від конвертерів систем числення на цьому сайті вартість зростає прямою лінією, а не квадратично: немає довжини, при якій сама арифметика стане проблемою. Обмеження нижче існують, бо таблиця по символах малюється в документі, а таблиця з мільйоном рядків і є тим, що реально зупиняє браузер.
Що таке відсоткове кодування?

Відсоткове кодування — також зване URL-кодуванням, а в старіших документах екрануванням — це правило, яке дозволяє URL містити символи, що йому заборонені. Відсоткове екранування — це три символи ASCII: знак відсотка і дві шістнадцяткові цифри, що означають один байт. %20 — це байт 0x20, тобто пробіл. %2F — це байт 0x2F, тобто коса риска. Більше нічого не задіяно: екранування називає байт, а не символ.

Оскільки воно називає байти, текст спершу треба перетворити на байти, а кодування байтів — UTF-8. Звідси беруться довгі екранування: один китайський ієрогліф — це три байти UTF-8, а отже три екранування, а один емодзі — чотири байти й дванадцять символів ASCII.

Версія на 30 секунд: пробіл — це %20 як значення URL, але + у даних форми; # — це %23; буквальний знак відсотка — це %25. Три кодувальники на цій сторінці однаково кодують літери, цифри і -_., і розходяться щодо десятка розділових знаків, серед них тильда — саме тому encodeURIComponent і encodeURI дають різні рядки для того самого тексту, і неправильний з двох може розбити значення на два параметри запиту.
Навіщо URL взагалі це потрібно

URI визначено як послідовність символів ASCII (RFC 3986, розділ 2.1), і в цьому діапазоні ASCII деякі символи зарезервовані: це розділові знаки, які надають URL структуру. Двокрапка і дві косі риски відокремлюють схему, знак @ відокремлює облікові дані користувача від хоста, знак питання починає рядок запиту, решітка починає фрагмент, амперсанд розділяє параметри запиту, а знак відсотка вводить екранування.

Це створює одну проблему і одну лазівку. Проблема: значення, яке саме містить один із цих символів — поле пошуку з текстом «a&b», назва файлу з косою рискою, пароль із двокрапкою, — інакше змінило б структуру URL, у якому подорожує. Лазівка: сам знак відсотка, тож кодувальник може просто розширити власний алфавіт. % записується як %25, а дві шістнадцяткові цифри після нього — звичайні незарезервовані символи.

Робочі приклади

Це точні результати трьох кодувальників, які запускає ця сторінка, на вхідних даних, обраних тому, що вони поводяться по-різному. Сторінка перераховує їх на ваших очах, а її самоперевірка перевіряє кожен рядок проти власних реалізацій платформи.

Три методи і коли кожен із них правильний

Коротка версія, яка рятує від більшості помилок: усе, що йде в одне значення параметра, проходить через encodeURIComponent (або через серіалізатор форм, який робить ту саму роботу з іншим безпечним набором), а повний уже зібраний URL проходить через encodeURI, якщо його взагалі треба екранувати. Вибір не того з цих двох — найпоширеніша помилка відсоткового кодування у вебкоді.

Які символи може містити URL, повністю
Два знаки плюс, два значення

Пробіл — єдиний символ, чия форма кодування залежить від того, куди прямує текст, і причина в знаку плюс.

Пастка сидить посередині цієї таблиці. У даних форми знак плюс означає пробіл, тож значення, яке справді містить знак плюс — номер телефону на кшталт +44 20 7946 0958, арифметичний вираз, адреса електронної пошти з тегом плюс, — має прийти як %2B. Якщо воно прийде як буквальний знак плюс, отримувач прочитає його як пробіл, і +44 прийде як " 44".

Закодовано двічі і як це розпізнати

Подвійне кодування виникає, коли рядок уже екранований, а потім екранується знову. Знак відсотка першого екранування — це звичайний символ, тож він стає %25, і нижній шар лишається видимим текстом.

Розпізнати це — справа підрахунку: декодуйте рядок один раз і подивіться на результат. Якщо він досі містить екранування, що виглядають навмисними — коректний %XX або знак плюс форми там, де очікується пробіл, — є ще один шар. Якщо там є %25, за яким йдуть дві шістнадцяткові цифри, наступний шар напевно є екрануванням.

Дві практичні примітки. По-перше, декодування двічі не є нешкідливим: це добре відомий спосіб змусити фільтр безпеки і код за ним бачити різні рядки, тож фільтр, що працює перед другим декодуванням, фільтрує не той текст. По-друге, кількість шарів тут — властивість рядка, а не обіцянка про намір: законне значення може мати один шар, а зламане — два.

Помилки, для виявлення яких створено цю сторінку
  • Значення, закодоване encodeURI. Роздільники лишаються, тож значення, що містить & або =, розбивається на кілька параметрів з іншого боку. Порівняйте два стовпці вгорі цієї сторінки для вхідних даних a&b=c: стовпець компонента екранує обидва символи, стовпець encodeURI їх залишає.
  • Буквальний знак плюс, надісланий як дані форми. Він прийде як пробіл. Використовуйте %2B.
  • Значення, екрановане двічі «для безпеки». Це не безпечніше. Отримувач декодує один раз і отримає рядок, який досі містить відсоткові екранування — та сама помилка, що й неправильний набір символів, лише на шар нижче.
  • Декодування того, що ніколи не було закодовано. Буквальний знак відсотка в тексті — 100% sure — не є екрануванням. У строгому читанні це зламане екранування, на позиції, яку повідомляє ця сторінка; у поблажливому читанні він лишається, тому поблажливе читання тут за замовчуванням, а не навпаки.
  • Припущення, що екранування має бути у верхньому регістрі. Декодування трактує %2f і %2F як один і той самий байт, і обидва коректні. Верхній регістр — лише канонічне написання, яке рекомендує RFC 3986 і яке створюватимуть інші інструменти.
  • Припущення, що відсоткове кодування робить текст безпечним. Воно робить текст однозначним усередині URL. Воно не зупиняє ін'єкцію, не є шифруванням і не є санітайзером: екранування має бути останнім кроком перед складанням, для того контексту, куди піде текст.
Короткі відповіді

“Чи є знак плюс пробілом в URL?”

Лише в даних форми і лише в частині запиту відправки форми. У шляху, фрагменті та в запиті звичайного посилання знак плюс — буквальний знак плюс. Тому ця сторінка має перемикач для цього, а не вгадує.

“Чому моє посилання показує %25?”

Тому що щось закодувало знак відсотка. Або буквальний знак відсотка пройшов через кодувальник — 100% sure стає 100%25%20sure — або рядок, що вже містив екранування, закодували вдруге. Декодуйте рядок один раз і подивіться, що вийде: якщо результат досі містить %XX, є ще один шар.

“Чи має значення, чи коса риска всередині значення запиту записана як %2F, чи як /?”

Для розбирача URL — ні: обидва доходять до сервера як коса риска, а набір запиту WHATWG залишає косу риску як є. Для сумісності — іноді так: багато серверних фреймворків виконують власне декодування над сирим рядком запиту, і деякі з них по-різному трактують символи, які ніколи не екранувалися. Коли значення складається вручну, а не серіалізатором, явне екранування зарезервованих символів — безпечніша звичка.

“Чому стовпець форми екранує тильду, яку два інші залишають?”

Тому що безпечний набір кодувальника форми вужчий: лише літери, цифри і -_.*. Кодувальник форм WHATWG екранує ~ ! ' ( ), хоча RFC 3986 вважає ~ незарезервованим. Обидва написання декодуються в те саме значення; різниця важлива лише при побайтовому порівнянні рядків.

“Чи можуть ті самі символи з'явитися без екранування в адресному рядку браузера?”

Так, і це адресний рядок, а не URL. Браузери показують читабельну форму URL і кодують її під час надсилання, тож 中文 у посиланні стає %E4%B8%AD%E6%96%87 у мережі. Копіюючи URL з адресного рядка, ви можете отримати будь-яку з форм залежно від браузера.

“Кожне екранування тут виглядає коректним, але символ, що виходить, не той, якого я очікував. Чому?”

За фразою «декодувалося неправильно» ховаються дві різні помилки, і сторінка повідомляє їх по-різному. Перша — зламане екранування: знак відсотка, за яким не йдуть дві шістнадцяткові цифри, як-от кінцевий 100% або %zz. Це синтаксична проблема: строге читання її відхиляє і каже, де вона розташована, а поблажливе зберігає як буквальний текст і перелічує. Друга — екранування, чий байт абсолютно законний, але байти не складаються в коректний символ UTF-8, як-от окремий %FF або трибайтовий символ, обрізаний як %E4%B8. Це не синтаксична проблема, тож обидва читання повертають текст, замінюють байти, які не можна декодувати, символом заміни U+FFFD («�») і виводять попередження, яке про це каже. Це попередження — корисна частина: текст, що приходить із символами заміни, зазвичай означає, що відправник використовував інший набір символів, або що рядок закодували на один раз більше. Відсоткове кодування називає байти, і ця сторінка, як і стандарт URL, завжди читає ці байти як UTF-8.

“Чого ця сторінка не робить?”

Вона не розбиває URL на схему, хост, шлях і запит — для цього призначено Розбирач URL. Вона не перевіряє, чи безпечно відкривати посилання, нічого не викликає і не вгадує набір символів байтів за екрануванням: вона завжди читає їх як UTF-8, як того вимагає стандарт URL.

“Який запасний варіант потрібен для декодування і звідки я знаю, що він правильний?”

Натисніть Самоперевірка. Вона порівнює власні кодувальники сторінки з encodeURIComponent, encodeURI і URLSearchParams платформи для кожного друкованого символу ASCII, знову запускає кожен приклад у таблицях вище і перевіряє записані ланцюжки шарів, потім показує тут кількість перевірок.

Стандарти і обсяг

Відсоткове кодування визначено в RFC 3986, розділ 2.1; визначення зарезервованих і незарезервованих символів — у розділах 2.2 і 2.3, а правило регістру — у розділі 6.2.2.1. Набори, які браузер справді використовує під час побудови URL, наведено в Стандарті URL WHATWG; вони розділені за контекстом: один набір для фрагмента, один для запиту, один для спеціального запиту, один для шляху, один для облікових даних користувача і вужчий, який використовується для окремого компонента. Надсилання форми, включно зі знаком плюс, описано в Стандарті HTML за application/x-www-form-urlencoded. Дві функції encodeURI і encodeURIComponent є частиною самого ECMAScript.

Ця сторінка заповнює стовпці шляху, запиту і фрагмента власними наборами для кожного контексту й виводить точний список для кожного контексту поруч із результатом. Ці набори походять від наборів WHATWG і відрізняються від них у двох навмисних місцях: хеш екранується всередині запиту, бо сирий хеш почав би фрагмент, а набір компонента зафіксовано точно на символах, які екранує encodeURIComponent. Там, де функція платформи є визначенням відповіді — encodeURIComponent, encodeURI і серіалізація форми, — ця функція викликається напряму, тож ці стовпці — це власна поведінка браузера, а не її переписана реалізація.

Текст сторінки написано для цього сайту. Кожне значення в таблицях вище створено власним кодом сторінки під час завантаження і перевірено проти реалізацій платформи в інструментах тестування цього сайту; самоперевірка повторює це порівняння у вашому браузері.