Mã hoá URL: encodeURI hay encodeURIComponent, + hay %20

Mã hoá phần trăm với kết quả thật: mỗi hàm JavaScript thoát ký tự nào, khi nào dấu cách là + và khi nào là %20, và những lỗi mã hoá hai lần.

Trong bài này

Một đường link bị hỏng, tìm kiếm C++ lại ra kết quả cho C, tên tệp tiếng Việt biến thành một tràng %E1%BB. Cả ba đều là lỗi mã hoá URL, và cả ba đều đến từ việc dùng đúng ý tưởng nhưng sai chỗ. Bài này cho thấy mã hoá phần trăm làm gì với chuỗi thật, ba kiểu mã hoá mà web thực sự dùng, và những lỗi gây ra các triệu chứng trên.

Mọi kết quả dưới đây đều do công cụ mã hoá, giải mã và phân tích URL tạo ra. Công cụ hiển thị cả ba kiểu mã hoá cạnh nhau và tách một URL thành từng phần.

Mã hoá phần trăm là gì

URL chỉ được chứa một tập nhỏ ký tự ASCII, và vài ký tự trong đó có nhiệm vụ riêng: / ngăn các đoạn đường dẫn, ? mở đầu phần truy vấn, & ngăn các tham số, = ngăn khoá với giá trị, # mở đầu phần neo. Ký tự nào không được phép, hoặc có thể bị nhầm thành một trong các dấu ngăn cách đó, sẽ được viết thành các byte UTF-8 của nó, mỗi byte là % và hai chữ số hệ thập lục phân.

Vì vậy é gồm hai byte C3 A9 và thành %C3%A9. Chữ ở trong tiếng Việt gồm ba byte và thành %E1%BB%9F. Emoji 🍜 gồm bốn byte, %F0%9F%8D%9C. Bản thân dấu phần trăm cũng phải được mã hoá thành %25, nếu không bộ giải mã sẽ cố đọc hai ký tự tiếp theo như số hex.

Ba kiểu mã hoá

Web dùng ba quy tắc hơi khác nhau, và chọn đúng quy tắc là phần lớn công việc.

`encodeURIComponent` dành cho một mảnh của URL: một đoạn đường dẫn, một khoá hay một giá trị truy vấn. Nó thoát mọi thứ trừ chữ cái, chữ số và - _ . ! ~ * ' ( ). Các dấu ngăn cách như / ? & = # đều bị thoát, vì bên trong một giá trị chúng chỉ là ký tự bình thường.

`encodeURI` dành cho cả một URL đã ghép xong. Nó giữ nguyên các dấu ; , / ? : @ & = + $ # để cấu trúc không bị phá, và chỉ thoát những gì hoàn toàn không được phép, như dấu cách và chữ không phải ASCII.

Mã hoá biểu mẫu (application/x-www-form-urlencoded) là thứ mà biểu mẫu HTML và URLSearchParams tạo ra. Nó viết dấu cách thành +, và thoát thêm vài ký tự mà encodeURIComponent để nguyên: ~ ! ' ( ).

Cùng các đầu vào qua cả ba kiểu:

Đầu vàoencodeURIComponentencodeURIbiểu mẫu
Phở & bún chảPh%E1%BB%9F%20%26%20b%C3%BAn%20ch%E1%BA%A3Ph%E1%BB%9F%20&%20b%C3%BAn%20ch%E1%BA%A3Ph%E1%BB%9F+%26+b%C3%BAn+ch%E1%BA%A3
a+b=ca%2Bb%3Dca+b=ca%2Bb%3Dc
50% off50%25%20off50%25%20off50%25+off
it's (fine)!~it's%20(fine)!~it's%20(fine)!~it%27s+%28fine%29%21%7E

Hãy nhìn dòng đầu. encodeURI để nguyên dấu &. Nếu bạn dùng nó để đưa Phở & bún chả vào giá trị truy vấn, máy chủ sẽ thấy giá trị kết thúc ở Phở , và sau dấu & là một tham số thứ hai tên bún chả không có giá trị.

Còn đây là cả một URL qua từng kiểu:

đầu vào:   https://example.com/tìm kiếm?q=phở bò&x=1#top
encodeURI: https://example.com/t%C3%ACm%20ki%E1%BA%BFm?q=ph%E1%BB%9F%20b%C3%B2&x=1#top
component: https%3A%2F%2Fexample.com%2Ft%C3%ACm%20ki%E1%BA%BFm%3Fq%3Dph%E1%BB%9F%20b%C3%B2%26x%3D1%23top

encodeURI cho ra một link dùng được. encodeURIComponent cho ra một chuỗi không còn là link nữa, nhưng lại đúng thứ bạn cần nếu cả URL này là giá trị của một tham số khác, ví dụ redirect= hay link chia sẻ.

Quy tắc thực hành: ghép URL từ các mảnh, và mã hoá từng mảnh bằng encodeURIComponent (hoặc để URLSearchParams lo phần truy vấn). Chỉ dùng encodeURI để làm gọn một URL hoàn chỉnh do ai đó gõ vào.

Dấu cách: + hay %20?

Cả hai đều đúng, ở những chỗ khác nhau. Trong phần truy vấn mã hoá theo kiểu biểu mẫu, + nghĩa là dấu cách. Trong đường dẫn, + là dấu cộng thật và dấu cách phải là %20. Sự nhầm lẫn còn theo chiều ngược lại: khi giải mã, bộ giải mã biểu mẫu biến + thành dấu cách, còn decodeURIComponent giữ nguyên +. Công cụ cho thấy sự khác biệt với a+b%20c: bộ giải mã component cho a+b c, bộ giải mã biểu mẫu cho a b c.

Đây chính là lỗi C++. Ô tìm kiếm đưa nguyên văn bản vào truy vấn sẽ tạo ra ?q=c++. Máy chủ đọc theo kiểu biểu mẫu sẽ giải mã cả hai dấu cộng thành dấu cách và tìm c theo sau là hai dấu cách. Mã hoá đúng thì truy vấn là q=c%2B%2B, và bộ phân tích đọc lại được c++.

Trình sửa truy vấn của công cụ nhận ra URL đang dùng kiểu nào và giữ nguyên, nên sửa một tham số sẽ không âm thầm đổi mọi dấu cách từ + sang %20.

Những lỗi giải mã nên hiểu

  • Dấu `%` lạc. 100% không giải mã nghiêm ngặt được, vì sau % phải có hai chữ số hex. decodeURIComponent báo lỗi; công cụ giữ nguyên dấu % như đã gõ và chỉ ra vị trí của nó.
  • UTF-8 bị hỏng. %E1%BB là hai byte đầu của một ký tự ba byte, đã bị cắt cụt. Thường là do chuỗi bị cắt theo giới hạn số byte. Công cụ báo lỗi thay vì tự bịa ra một ký tự.
  • Mã hoá hai lần. %25E2%2582%25AC giải mã một lần thành %E2%82%AC, giải mã lần nữa mới ra €. Nếu thấy %25 theo sau là chữ số hex trong URL, nghĩa là có thứ đã mã hoá một chuỗi vốn đã được mã hoá. Hãy sửa đoạn mã mã hoá hai lần; giải mã hai lần "cho chắc" sẽ làm hỏng những giá trị thật sự chứa %.

Những lỗi thường gặp

  1. Dùng `encodeURI` cho một giá trị. Nó để nguyên & = ? #, nên giá trị chứa các ký tự này làm hỏng truy vấn.
  2. Nối văn bản thô vào URL. "?q=" + text chạy ổn cho tới khi ai đó gõ &, # hoặc +.
  3. Mã hoá cả URL bằng `encodeURIComponent` rồi dùng làm link. :// thành %3A%2F%2F và trình duyệt coi nó là đường dẫn tương đối.
  4. Giải mã hai lần. Biến một %25 hợp lệ thành %, và giá trị như 100%25 thành lỗi.
  5. Nghĩ rằng `+` luôn là dấu cách. Chỉ đúng trong truy vấn mã hoá kiểu biểu mẫu.

Nếu bạn đang tạo link chiến dịch, công cụ tạo link UTM tự mã hoá mọi giá trị và giữ nguyên truy vấn cùng phần neo sẵn có; bài quy ước đặt tên UTM nói nên điền gì vào các giá trị đó. Với mọi trường hợp khác, hãy dán URL vào công cụ mã hoá và giải mã URL và đọc từng phần trước khi đưa vào sử dụng.

Công cụ được nhắc trong bài

Về trang blog