Pemeriksa Pengodean Persen

Pengodean persen bukan satu aturan melainkan beberapa, dan mereka tidak sepakat tentang sekitar selusin karakter. Halaman ini menjalankan teks yang sama melalui encodeURIComponent, encodeURI, dan serialisasi application/x-www-form-urlencoded berdampingan, menandai karakter tempat mereka tidak sepakat, mencetak satu baris per karakter dengan byte UTF-8 dan kelas RFC 3986-nya, serta menyatakan berapa kali sebuah string telah dienkode. Sisi dekode membaca string dengan aturan ketat atau longgar dan memberi tahu escape mana yang rusak serta di mana, alih-alih mengembalikan hasil yang diam-diam salah.
Semua pemrosesan berlangsung secara lokal di browser — data Anda tidak pernah diunggah ke server mana pun.

Tidak yakin apa itu pengodean persen? Pengodean persen dijelaskan dengan bahasa sederhana — apa itu, mengapa spasi menjadi %20 atau +, dan apa artinya ketika URL menampilkan %25.
Apa yang dilakukan ini:
Cara membaca escape:
encodeURIComponent
encodeURI
form (application/x-www-form-urlencoded)
Ctrl+Enter di input menjalankan mode yang aktif.
Pilih mode dan klik tombol aksinya. Enkode & periksa menampilkan tiga pengodean teks Anda plus satu baris per karakter; Dekode membaca escape kembali dan menghitung lapisannya. Tombol Self-test menjalankan ulang setiap contoh yang didokumentasikan, setiap karakter ASCII yang dapat dicetak, dan rantai lapisan yang tercatat dalam berkas data halaman ini, lalu melaporkan jumlahnya di sini.
Di mana pengodean tidak sepakat:hanya karakter di bawah ini yang diperlakukan berbeda oleh ketiga metode. Lainnya melewati ketiganya tanpa berubah, atau di-escape identik oleh ketiganya.
Setiap karakter, satu baris masing-masing:byte UTF-8 yang menjadi karakter, kelas yang diberikan RFC 3986 kepadanya, dan escape yang ditulis setiap konteks. Keempat kolom konteks adalah set pengodean persen URL WHATWG, yang digunakan peramban saat membangun path, kueri, atau fragmen; baris yang diberi warna adalah karakter yang diperlakukan berbeda oleh ketiga pengodean.
Himpunan persis yang di-escape halaman ini untuk setiap bagian URL:dicetak langsung dari pengodean itu sendiri, sehingga tabel ini dan kode yang mengisi kolom di atas tidak dapat menyimpang. Setiap set juga meng-escape karakter kontrol C0 (U+0000 hingga U+001F) dan setiap titik kode di atas U+007E, itulah sebabnya teks non-ASCII selalu di-escape di setiap konteks.
Set-set ini diturunkan dari set pengodean persen WHATWG dan berbeda darinya di dua tempat yang disengaja, yang keduanya membuat perakitan manual aman: tanda pagar di-escape di dalam kueri, karena yang mentah akan memulai fragmen; dan set komponen dipatok tepat pada karakter yang di-escape encodeURIComponent, termasuk tanda kutip tunggal, sementara set formulir dipatok pada apa yang di-escape URLSearchParams. Kedua kolom dalam tabel di atas karena itu adalah perilaku platform sendiri, bukan implementasi ulang. Daftar itu juga menjelaskan hasil yang sering mengejutkan orang: tanda persen di-escape oleh encodeURIComponent dan encodeURI, tetapi tidak ada dalam set konteks WHATWG mana pun, sehingga kolom path, kueri, dan fragmen dalam tabel per karakter membiarkannya. Tanda persen mentah dalam URL yang Anda rakit sendiri tetap merupakan risiko, karena standar mengharapkan masukan sudah divalidasi lebih dulu: %41 di dalam kueri adalah satu A bagi banyak framework server, dan pembacaan ketat halaman ini memperlakukan % sendirian sebagai escape yang rusak. Meng-escape-nya sendiri sebagai %25 adalah kebiasaan yang lebih aman, dan tabel per karakter menampilkan kedua ejaan berdampingan.
Berapa kali string ini telah dienkode:setiap putaran dekode adalah satu lapisan. Nilai yang telah dienkode dua kali dimulai dengan%25tempat satu pengodean tunggal akan meninggalkan%20, dan nilai yang telah dienkode tiga kali dimulai dengan%2525.
Karakter yang boleh dibawa URL apa adanya:RFC 3986 membagi rentang ASCII menjadi tiga kelompok, dan aturan escape hanya masuk akal terhadap pembagian itu.
Karakter reserved tidak dilarang, mereka dipakai: mereka memiliki arti bagi pengurai URL. Aturan yang mengikuti dari daftar itu singkat — jika sebuah karakter reserved dan Anda memaksudkannya sebagai data, escape; jika ia unreserved, Anda tidak pernah perlu meng-escape-nya.
Tiga metode, dan apa yang dibiarkan masing-masing:
Daftar-daftar ini adalah himpunan persis yang dibiarkan tidak di-escape oleh setiap metode dalam rentang ASCII; Self-test memeriksa setiap karakter ASCII yang dapat dicetak terhadapnya, sehingga tabel tidak dapat menyimpang dari pengodean yang dideskripsikannya.
Seberapa besar masukan yang diterima halaman ini, dan mengapa:pengodean persen adalah satu pass tunggal atas teks, sehingga tidak seperti konverter radix di situs ini, biayanya tumbuh secara lurus alih-alih kuadrat: tidak ada panjang di mana aritmetikanya sendiri menjadi masalah. Batas di bawah ada karena tabel per karakter digambar di dokumen, dan tabel dengan satu juta baris itulah yang benar-benar menghentikan peramban.
Apa itu pengodean persen?

Pengodean persen — juga disebut pengodean URL, dan dalam dokumen lama disebut escaping — adalah aturan yang memungkinkan URL membawa karakter yang tidak boleh ia miliki. Escape persen adalah tiga karakter ASCII: tanda persen dan dua digit heksadesimal, yang mewakili satu byte. %20 adalah byte 0x20, yaitu spasi. %2F adalah byte 0x2F, yaitu garis miring. Tidak ada yang lain yang terlibat: escape menamai byte, bukan karakter.

Karena ia menamai byte, teks harus diubah menjadi byte terlebih dahulu, dan pengodean byte-nya adalah UTF-8. Dari situlah escape panjang berasal: satu karakter Mandarin adalah tiga byte UTF-8 dan karenanya tiga escape, dan satu emoji adalah empat byte dan dua belas karakter ASCII.

Versi 30 detik: spasi adalah %20 sebagai nilai URL tetapi + dalam data formulir; # adalah %23; tanda persen literal adalah %25. Ketiga pengodean di halaman ini sepakat tentang huruf, digit, dan -_., dan tidak sepakat tentang sekitar selusin karakter tanda baca, termasuk tilde — itulah sebabnya encodeURIComponent dan encodeURI menghasilkan string berbeda untuk teks yang sama, dan mengapa yang salah dari keduanya dapat memecah nilai menjadi dua parameter kueri.
Mengapa URL membutuhkannya sama sekali

URI didefinisikan sebagai urutan karakter ASCII (RFC 3986 bagian 2.1), dan di dalam rentang ASCII itu beberapa karakter adalah dicadangkan: mereka adalah tanda baca yang memberi URL strukturnya. Titik dua dan dua garis miring memisahkan skema, tanda at memisahkan informasi pengguna dari host, tanda tanya memulai kueri, tanda pagar memulai fragmen, ampersand memisahkan parameter kueri, dan tanda persen memperkenalkan escape.

Itu menciptakan satu masalah dan satu celah. Masalahnya: nilai yang berisi salah satu karakter tersebut — kotak pencarian yang berisi "a&b", nama berkas yang berisi garis miring, kata sandi yang berisi titik dua — jika tidak di-escape akan mengubah struktur URL tempat ia dibawa. Celahnya: tanda persen itu sendiri, sehingga pengodean dapat memperluas alfabetnya sendiri. % ditulis sebagai %25, dan dua digit heksadesimal setelahnya adalah karakter unreserved biasa.

Contoh yang dikerjakan

Ini adalah keluaran persis dari ketiga pengodean yang dijalankan halaman ini, dengan masukan yang dipilih karena mereka berperilaku berbeda satu sama lain. Halaman menghitung ulang di depan Anda, dan Self-test-nya memeriksa ulang setiap baris terhadap implementasi platform itu sendiri.

Tiga metode, dan kapan masing-masing tepat

Kaidah singkat yang menyelamatkan sebagian besar bug: apa pun yang masuk ke satu nilai parameter melewati encodeURIComponent (atau melalui serialisasi formulir, yang melakukan tugas yang sama dengan set aman yang berbeda), dan URL lengkap yang sudah dirakit melewati encodeURI jika memang perlu di-escape. Mengambil salah satu dari dua itu secara keliru adalah kesalahan pengodean persen yang paling umum dalam kode web.

Karakter yang boleh dibawa URL, secara lengkap
Dua tanda plus, dua arti

Spasi adalah satu-satunya karakter yang pengodeannya bergantung pada ke mana teks akan pergi, dan tanda plus adalah alasannya.

Jebakannya ada di tengah tabel itu. Dalam data formulir tanda plus berarti spasi, sehingga nilai yang benar-benar berisi tanda plus — nomor telepon yang ditulis +44 20 7946 0958, ekspresi aritmetika, alamat surel dengan tag plus — harus berpindah sebagai %2B. Jika berpindah sebagai tanda plus literal, penerima membacanya kembali sebagai spasi, dan +44 tiba sebagai " 44".

Dikodekan dua kali, dan cara mengenalinya

Pengodean ganda terjadi ketika sebuah string sudah di-escape lalu di-escape lagi. Tanda persen dari escape pertama adalah karakter seperti karakter lainnya, sehingga menjadi %25, dan lapisan di bawahnya bertahan sebagai teks yang terlihat.

Mengenalinya adalah soal menghitung: dekode string sekali dan lihat hasilnya. Jika masih berisi escape yang tampak disengaja — %XX yang valid atau tanda plus formulir di tempat yang seharusnya spasi — ada lapisan lain. Jika berisi %25 diikuti dua digit heksadesimal, lapisan berikutnya pasti escape.

Dua catatan praktis. Pertama, mendekode dua kali tidak tidak berbahaya: itu adalah cara terkenal untuk membuat filter keamanan dan kode di baliknya melihat string yang berbeda, sehingga filter yang berjalan sebelum dekode kedua memfilter teks yang salah. Kedua, hitungan lapisan di sini adalah sifat string, bukan janji tentang maksud: nilai yang sah bisa berlapis satu dan yang rusak bisa berlapis dua.

Kesalahan yang dirancang halaman ini untuk ditangkap
  • Nilai yang dienkode dengan encodeURI. Pemisah tetap utuh, sehingga nilai yang berisi & atau = terpecah menjadi beberapa parameter di sisi lain. Bandingkan dua kolom di bagian atas halaman ini untuk masukan a&b=c: kolom komponen meng-escape kedua karakter, kolom encodeURI membiarkannya.
  • Tanda plus literal yang dikirim sebagai data formulir. Ia diterima sebagai spasi. Gunakan %2B.
  • Nilai yang di-escape dua kali "agar aman". Itu tidak lebih aman. Penerima mendekode sekali dan mendapatkan string yang masih berisi escape persen, yang merupakan kegagalan yang sama seperti set karakter yang salah, hanya satu lapisan di bawahnya.
  • Mendekode apa yang tidak pernah dienkode. Tanda persen literal dalam teks — 100% sure — bukan escape. Dalam pembacaan ketat ia adalah escape yang rusak, pada posisi yang dilaporkan halaman ini; dalam pembacaan longgar ia bertahan, itulah sebabnya longgar adalah default di sini, bukan sebaliknya.
  • Mengira escape harus huruf kapital. Dekode memperlakukan %2f dan %2F sebagai byte yang sama, dan keduanya valid. Huruf kapital hanyalah ejaan kanonik yang direkomendasikan RFC 3986, dan yang akan dihasilkan alat lain.
  • Mengira pengodean persen membuat teks aman. Ia membuat teks tidak ambigu di dalam URL. Ia tidak menghentikan injeksi, bukan enkripsi, dan bukan sanitizer: escape harus menjadi langkah terakhir sebelum perakitan, untuk konteks tempat teks itu akan masuk.
Jawaban singkat

“Apakah tanda plus berarti spasi dalam URL?”

Hanya dalam data formulir, dan hanya di bagian kueri dari pengiriman formulir. Dalam path, dalam fragmen, dan dalam kueri tautan biasa, tanda plus adalah tanda plus literal. Itulah sebabnya halaman ini memiliki sakelar untuknya alih-alih menebak.

“Mengapa tautan menampilkan %25?”

Karena sesuatu mengodekan tanda persen. Entah tanda persen literal melewati pengodean — 100% sure menjadi 100%25%20sure — atau string yang sudah berisi escape dikodekan lagi. Dekode string sekali dan lihat apa yang kembali: jika hasilnya masih berisi %XX, ada lapisan lain.

“Apakah penting jika garis miring di dalam nilai kueri berupa %2F atau /?”

Bagi pengurai URL, tidak: keduanya sampai ke server sebagai garis miring, dan set kueri WHATWG membiarkan garis miring tetap. Bagi interoperabilitas, kadang ya: banyak framework server menjalankan dekode mereka sendiri atas string kueri mentah, dan beberapa di antaranya tidak sepakat tentang karakter yang tidak pernah di-escape. Ketika sebuah nilai dirakit dengan tangan alih-alih oleh serializer, meng-escape karakter reserved secara eksplisit adalah kebiasaan yang lebih aman.

“Mengapa kolom formulir meng-escape tilde yang dibiarkan dua lainnya?”

Karena himpunan aman pengodean formulir lebih sempit: hanya huruf, digit, dan -_.*. Pengodean formulir WHATWG meng-escape ~ ! ' ( ) meskipun RFC 3986 mencantumkan ~ sebagai unreserved. Kedua ejaan didekode ke nilai yang sama; perbedaannya hanya berarti ketika membandingkan string byte demi byte.

“Bisakah karakter yang sama muncul tanpa escape di bilah alamat peramban?”

Ya, dan itu adalah bilah alamat, bukan URL. Peramban menampilkan bentuk URL yang dapat dibaca dan mengodekannya saat mengirim, sehingga 中文 dalam tautan menjadi %E4%B8%AD%E6%96%87 di kabel. Salin URL dari bilah alamat dan Anda mungkin mendapatkan salah satu bentuk tergantung peramban.

“Setiap escape di sini tampak valid, tetapi karakter yang keluar bukan yang diharapkan. Mengapa?”

Dua kegagalan berbeda bersembunyi di balik "salah dekode", dan halaman melaporkannya secara berbeda. Yang pertama adalah escape yang rusak: tanda persen yang tidak diikuti dua digit heksadesimal, seperti 100% di akhir atau %zz. Itu masalah sintaks, dan pembacaan ketat menolaknya dan menyebutkan letaknya, sementara pembacaan longgar menyimpannya sebagai teks literal dan mendaftarkannya. Yang kedua adalah escape yang byte-nya sah tetapi byte-byte-nya tidak membentuk karakter UTF-8 yang valid, seperti %FF sendirian atau karakter tiga byte yang terpotong sebagai %E4%B8. Itu bukan masalah sintaks, sehingga kedua pembacaan mengembalikan teks, mengganti byte yang tidak dapat didekode dengan karakter pengganti U+FFFD ("�"), dan mendaftarkan peringatan yang mengatakannya. Peringatan itulah bagian yang berguna: teks yang datang sebagai karakter pengganti biasanya berarti pengirim memakai set karakter lain, atau string itu di-escape sekali terlalu sering. Pengodean persen menamai byte, dan halaman ini, seperti standar URL, selalu membaca byte tersebut sebagai UTF-8.

“Apa yang tidak dilakukan halaman ini?”

Ia tidak memecah URL menjadi skema, host, path, dan kueri — untuk itulah Pengurai URL. Ia tidak memeriksa apakah tautan aman dibuka, tidak menghubungi apa pun, dan tidak menebak set karakter dari byte di balik escape: ia selalu membacanya sebagai UTF-8, seperti yang diwajibkan standar URL.

“Fallback apa yang dibutuhkan dekode, dan bagaimana tahu bahwa itu benar?”

Tekan Self-test. Ia membandingkan pengodean halaman ini sendiri dengan encodeURIComponent, encodeURI, dan URLSearchParams milik platform untuk setiap karakter ASCII yang dapat dicetak, menjalankan ulang setiap contoh di tabel di atas, dan memeriksa ulang rantai lapisan yang tercatat, lalu melaporkan jumlah pemeriksaan di sini.

Standar dan ruang lingkup

Pengodean persen dispesifikasikan dalam RFC 3986 bagian 2.1, dengan definisi karakter reserved dan unreserved di bagian 2.2 dan 2.3 dan aturan huruf di bagian 6.2.2.1. Himpunan yang benar-benar digunakan peramban saat membangun URL ada dalam Standar URL WHATWG, yang membaginya menurut konteks: satu untuk fragmen, satu untuk kueri, satu untuk kueri khusus, satu untuk path, satu untuk informasi pengguna, dan yang lebih sempit untuk satu komponen. Pengiriman formulir, termasuk tanda plus, ada dalam Standar HTML di bawah application/x-www-form-urlencoded. Kedua fungsi encodeURI dan encodeURIComponent adalah bagian dari ECMAScript itu sendiri.

Halaman ini mengisi kolom path, kueri, dan fragmen dari set per konteksnya sendiri, dan mencetak daftar persis untuk setiap konteks di samping hasilnya. Set-set itu diturunkan dari set WHATWG dan berbeda darinya di dua tempat yang disengaja: tanda pagar di-escape di dalam kueri, karena tanda pagar mentah akan memulai fragmen, dan set komponen dipatok tepat pada karakter yang di-escape encodeURIComponent. Jika fungsi platform adalah definisi jawabannya — encodeURIComponent, encodeURI, dan serialisasi formulir — fungsi itu dipanggil langsung, sehingga kolom tersebut adalah perilaku peramban sendiri, bukan implementasi ulang.

Teks halaman ditulis untuk situs ini. Setiap nilai dalam tabel di atas dihasilkan oleh kode halaman itu sendiri saat dimuat dan telah diverifikasi terhadap implementasi platform dalam perkakas pengujian situs ini; Self-test mengulang perbandingan itu di peramban Anda.