Công cụ dành cho nhà phát triển · Bộ mã hóa và giải mã URL
escape() so với EncodeURIComponent: mã hóa URL của JavaScript đã phát triển như thế nào
· Lý lịch
javascript url-encoding lịch sử
JavaScript đã có ba thế hệ hàm mã hóa URL và phiên bản cũ nhất vẫn còn ẩn trong mã sản xuất. Bài đăng này giải thích những gì escape() làm sai, tại sao ES3 lại thêm các hàm URI và tại sao chúng lại giữ nguyên ! * ' ( ).
%u20AC trong nhật ký cũ — dấu vân tay không thể nhầm lẫn của escape() và lỗi giải mã mà nó gây ra
Tệp JavaScript kế thừa chứa lệnh gọi mã hóa URL bằng cách sử dụng hàm escape() không được dùng nữa. Đầu ra trong tệp nhật ký hoặc thông báo lỗi bao gồm chuỗi %u20AC—một dấu vết không thể nhầm lẫn của hàm escape() không được dùng nữa mà không ai khác sử dụng. Trình tự này không khớp với bất kỳ mã hóa URL tiêu chuẩn nào và bộ giải mã được xây dựng trên các quy tắc RFC 3986 hoặc WHATWG sẽ không nhận ra nó. Dữ liệu không thể quay vòng thông qua các công cụ hiện đại. Đó là dấu hiệu phổ biến của mã có trước ES3 và chưa được cập nhật kể từ những năm 1990.
Hàm escape() được thiết kế trong thời đại Netscape, trước khi JavaScript có các tiêu chuẩn hoặc quy tắc mã hóa URL chính thức. Nó mã hóa hầu hết các ký tự không phải ASCII bằng cách sử dụng ký hiệu %uXXXX, mã thập lục phân gồm bốn chữ số mà không ai khác sử dụng và không có tiêu chuẩn nào xác định ở bất kỳ đâu. Điều này có ý nghĩa đối với việc sử dụng một lần trong trình duyệt nhưng nó phá vỡ tính tương thích với các tiêu chuẩn URL và khiến dữ liệu không thể giải mã được ở nơi khác.
escape() và unescape(): một thiết kế thời Netscape — các giả định Latin-1, phát minh %uXXXX và tại sao nó không bao giờ phù hợp với bất kỳ tiêu chuẩn nào
escape() và unescape() giả sử đầu vào là Latin-1 (ISO 8859-1), mã hóa ký tự có trước UTF-8 và Unicode. Họ chuyển đổi từng ký tự thành mã thập lục phân, sử dụng %XX cho các ký tự Latin-1 bit cao và %uXXXX cho mọi thứ bên ngoài Latin-1. Không thể thể hiện một ký tự không phải tiếng Latinh-1 như biểu tượng cảm xúc. Các chức năng rất đơn giản và nhanh chóng, nhưng chúng cũng hoàn toàn sai đối với bất kỳ trường hợp sử dụng hiện đại nào.
Cả hai chức năng đều được thêm vào JavaScript trước khi có tiêu chuẩn. Chúng không còn được dùng nữa ngay sau khi ES3 giới thiệu mã hóa URL thích hợp trong 1999. Chúng vẫn ở trong JavaScript để có khả năng tương thích ngược—việc loại bỏ chúng sẽ phá vỡ mã cổ. Nhưng bất kỳ mã mới nào cũng không bao giờ nên sử dụng chúng. Chúng là một di tích di sản.
ES3 (1999) thêm mã hóaURI và mã hóaURIComponent — UTF-8 mã hóa phần trăm được căn chỉnh với RFC 2396
ES3 đã giới thiệu hai hàm: mã hóaURI và mã hóaURIComponent. Cả hai đều thực hiện UTF-8 mã hóa phần trăm: chuyển đổi các ký tự không phải ASCII thành UTF-8 byte, sau đó ghi từng byte dưới dạng %HH. Cả hai đều phù hợp với RFC 2396 hiện hành vào thời điểm đó. RFC 3986 xuất hiện sau và không thay đổi hành vi mã hóa. Những chức năng này vẫn là tiêu chuẩn ngày nay và nên được sử dụng.
EncodeURI dùng để mã hóa URI hoàn chỉnh; EncodeURIComponent dùng để mã hóa một thành phần bên trong URI, như giá trị truy vấn hoặc đoạn đường dẫn. Sự khác biệt là hoàn toàn quan trọng và dễ hiểu lầm. EncodeURI bảo tồn các ký tự cấu trúc như : / ? # @ = & và ;. EncodeURIComponent mã hóa tất cả những thứ đó, giúp chúng an toàn khi nhúng vào bên trong URI lớn hơn.
Tại sao ! * ' ( ) vẫn chưa được mã hóa — các ký tự RFC 2396 'đánh dấu' được cố định trong ngôn ngữ sau khi RFC 3986 di chuyển chúng
Cả hai hàm đều không mã hóa các ký tự này: chữ cái, chữ số, dấu gạch ngang (-), dấu gạch dưới (_), dấu chấm (.), dấu ngã (~) và năm dấu chấm câu ! * ' ( ). Các nhãn hiệu đến từ RFC 2396, liệt kê chúng dưới dạng ký tự "dấu" không được bảo lưu. RFC 3986 xuất hiện trong 2005 và chuyển năm sản phẩm đó sang một danh mục khác, nhưng JavaScript đã đóng băng mã hóaURI và mã hóaURIComponent trong 1999. Việc thay đổi những ký tự mà họ để lại sẽ phá vỡ mã hiện có, vì vậy họ vẫn giữ nguyên.
Quyết định giữ năm dấu đó không được mã hóa để tương thích ngược có nghĩa là mã hóa của JavaScript không hoàn toàn khớp với tiêu chuẩn RFC 3986 hoặc tiêu chuẩn WHATWG. Nó đủ gần để sử dụng thực tế và việc thay đổi nó bây giờ là hoàn toàn không thể. Đây là bài học về sự ổn định API: một khi bạn cố định hành vi, bạn không thể thay đổi hành vi đó ngay cả khi tiêu chuẩn phát triển.
Ví dụ đã hoạt động: cùng một chuỗi thông qua lối thoát, mã hóaURI và mã hóaURIComponent - ba kết quả đầu ra được so sánh
Lấy chuỗi "R&D (nghiên cứu) = quán cà phê". Chạy nó thông qua escape(), EncodeURI và EncodeURIComponent. escape() tạo ra "R%26D%20(research)%20%3D%20caf%E9's", trộn các dấu ngoặc đơn và dấu nháy đơn không được mã hóa với ký hiệu và dấu bằng được mã hóa phần trăm. EncodeURI tạo ra "R&D%20(research)%20=%20caf%C3%A9's", chỉ để lại dấu và và dấu bằng vì chúng có cấu trúc. EncodeURIComponent tạo ra "R%26D%20%28research%29%20%3D%20caf%C3%A9%27s", mã hóa mọi thứ kể cả dấu ngoặc đơn và dấu nháy đơn.
Dán cùng một chuỗi vào bộ mã hóa & giải mã URL rồi chuyển đổi giữa mã hóaURI và mã hóaURIComponent để thấy sự khác biệt. Sau đó kiểm tra xem escape() sẽ tạo ra cái gì (bạn có thể gọi nó trong bảng điều khiển trình duyệt, mặc dù nó sẽ cảnh báo bạn). Bạn sẽ thấy ngay rằng ba hàm này tạo ra ba kết quả hoàn toàn khác nhau.
Di chuyển khỏi escape() — ánh xạ các lệnh gọi cũ sang hàm hiện đại bên phải và xử lý dữ liệu %uXXXX được lưu trữ
Mã cũ sử dụng escape() phải được cập nhật. Nếu escape() được sử dụng để mã hóa thành phần URI, hãy thay thế nó bằng EncodeURIComponent. Nếu nó được sử dụng để mã hóa URI hoàn chỉnh, hãy sử dụng EncodeURI. Đối với dữ liệu được lưu trữ chứa các chuỗi %uXXXX, bạn cần có bộ giải mã tùy chỉnh: chuyển đổi từng chuỗi %uXXXX thành một điểm mã Unicode, sau đó thu thập các điểm mã thành một chuỗi. unescape() tích hợp của JavaScript sẽ đọc %uXXXX nhưng kết quả có thể không chính xác UTF-8.
Sau khi thay thế escape(), hãy kiểm tra mã bằng các chuỗi chứa ký tự không phải ASCII, dấu câu và ký tự đặc biệt. Đầu ra bây giờ phải phù hợp với những gì các công cụ và tiêu chuẩn hiện đại mong đợi. Nếu mã của bạn có trước ES3 đáng kể thì mã đó cũng có thể sử dụng các mẫu lỗi thời khác; một cuộc kiểm toán toàn diện là đáng nỗ lực.
Điều này không bao gồm những gì — API URL và URLSearchParams, được đề cập riêng
API URL và URLSearchParams, được thêm vào sau này, cung cấp giao diện cấp cao hơn cho việc mã hóa thành phần và xây dựng URL. Chúng tự động xử lý tất cả các lần thoát và khớp chính xác với tiêu chuẩn WHATWG URL. Chúng là cách ưa thích để xây dựng URL theo chương trình theo JavaScript hiện đại.
Bài đăng này chỉ đề cập đến các chức năng mã hóa chứ không đề cập đến các API cấp cao hơn. URL và URLSearchParams phân tích cú pháp cấu trúc, chọn quy tắc thành phần và tuần tự hóa kết quả, trong khi EncodeURIComponent biến đổi một chuỗi được cung cấp mà không biết chuỗi đó sẽ được đặt ở đâu. Sự khác biệt đó chính là ranh giới: di chuyển một lệnh gọi escape() cũ tùy theo việc nó xử lý một giá trị hay một địa chỉ, sau đó xem xét thay thế việc ghép nối thủ công xung quanh bằng các API có cấu trúc như một công cụ tái cấu trúc riêng biệt.
Bài học rút ra: ba hàm, một cặp còn sót lại — cách bộ mã hóa và giải mã URL thể hiện hành vi của bộ mã hóaURI và bộ mã hóaURIComponent hiện đại cạnh nhau
Quá trình phát triển JavaScript hiện đại nên sử dụng mã hóaURI hoặc mã hóaURIComponent, không bao giờ thoát(). Các chức năng đã được chuẩn hóa trong 1999 và không thay đổi kể từ đó. Chúng mã hóa các ký tự không phảiASCII dưới dạng UTF-8 byte và xử lý chính xác các ký tự dành riêng tiêu chuẩn. Công cụ mã hóa & giải mã URL triển khai cả hai chức năng và cho phép bạn xem hành vi của chúng cạnh nhau, giúp bạn dễ dàng chọn chức năng phù hợp cho thành phần của mình.
Nếu bạn gặp các chuỗi %u trong nhật ký cũ hoặc dữ liệu được lưu trữ thì chúng là đầu ra escape() và cần được di chuyển. Việc di chuyển rất đơn giản khi bạn xác định được mẫu. Mã hiện đại không bao giờ nên tạo ra chúng.