【筆記】關於 URL Encoding

  • 14543
  • 0
  • 2018-12-19

  我們知道在 .NET 平臺中有著不同的 URL 編碼方法,但哪個方法才是符合我們需求的方法呢?


  我們知道在 .NET 平臺中有著不同的 URL 編碼方法,但哪個方法才是符合我們需求的方法呢?我們接下來會進行分析以評估我們需要的方法。

  在這份筆記中包含下列內容:

  1. 使用案例
  2. 什麼是 URL Encoding
  3. 可能遭遇的問題
  4. .NET 平臺常見的 URL 編碼方法
  5. 分析與比較
  6. 更好的解決方案?
  7. 結論
  8. 參考資料

 

  1. 1. 使用案例

      當需要在 URL 的 Query String 中傳遞一組 URL 時,我們會透過 URL 編碼將要傳遞的 URL 進行編碼,如下所示:

    • 輸入:
      http://test.test/~path?Num=123&Space=" "&Symbol=(!*)

    • HttpUtility.UrlEncode:
      http%3a%2f%2ftest.test%2f%7epath%3fNum%3d123%26Space%3d%22+%22%26Symbol%3d(!*)

    • WebUtility.UrlEncode:
      http%3A%2F%2Ftest.test%2F%7Epath%3FNum%3D123%26Space%3D%22+%22%26Symbol%3D(!*)

    • Uri.EscapeDataString:
      http%3A%2F%2Ftest.test%2F~path%3FNum%3D123%26Space%3D%22%20%22%26Symbol%3D%28%21%2A%29

      有什麼問題?

    • 輸入:
      http://test.test/~path?Num=123&Space=" "&Symbol=(!*)

    • HttpUtility.UrlEncode:
      http%3a%2f%2ftest.test%2f%7epath%3fNum%3d123%26Space%3d%22+%22%26Symbol%3d(!*)

    • WebUtility.UrlEncode:
      http%3A%2F%2Ftest.test%2F%7Epath%3FNum%3D123%26Space%3D%22+%22%26Symbol%3D(!*)

    • Uri.EscapeDataString:
      http%3A%2F%2Ftest.test%2F~path%3FNum%3D123%26Space%3D%22%20%22%26Symbol%3D%28%21%2A%29

     

  2. 2. 什麼是 URL Encoding

      首先我們先瞭解什麼是 URL Encoding,其目的、方法與機制。

    • 目的與方法:

      • 目的:為了在 URI 識別字串序列資源。
      • 方法:使用 Percent-Encoding 機制。
    • Percent-Encoding:

      • Percent-Encoding 也稱作 URL Encoding,用於表示允許的字元集合外,字元的 8 位元位元組 (8-bit bytes) 序列。
      • 由百分號 % 後跟兩個十六進位數字組成。
      • 為保持一致性生產器 (producers) 與正規器 (normalizers) 對所有 percent-encodings 使用大寫十六進位數字。
      • Percent-encoded = “%”HEXDIG HEXDIG
      • 範例:
        • US-ASCII 問號「?」
        • 二進位:0011 1111
        • 十六進位:3F
        • Percent-encoded:%3F

     

  3. 3. 可能遭遇的問題

      在瞭解 URL Encoding 後,我們來探討在編碼時可能會遇到的問題以及需要留意的地方。

    • 須編碼與不須編碼的字元

        首先,我們需要瞭解什麼字元要編碼什麼字元不用編碼,在 RFC 3986 URI: Generic Syntax 請求意見稿中有下列方類:

      • 保留字元(在 URI 中合法,有特殊涵義):
        :/?#[]@!$&'()*+,;=

      • 未保留字元(在 URI 中合法):
        英文字母、數字與-._~

      • 其他字元(在 URI 中非法,必須編碼):
        不包含在保留與未保留中的字元

    • 空格字元的編碼

        再來,我們也發現不同方法對於空格字元的編碼結果不逕相同,主要是 RFC 3986 與 HTML 4.01 的定義不同,而這兩種編碼結果都有在使用。

      • RFC 3986 URI: Generic Syntax
        Percent-encoded:%20

      • W3C HTML 4.01
        application/x-www-form-urlencoded:+

    • 最大 URL 長度

        還有,可能需要注意的是 URL 的最大長度,在不同的網頁伺服器或瀏覽器環境中支援的最大長度也有所不同,以下先列出不分資訊作為參考。

      • RFC 2616 Hypertext Transfer Protocol -- HTTP/1.1:
        無指定任何 URL 長度的要求。

      • RFC 3986 URI: Generic Syntax:
        URI 網域名稱最長為 255 個字元(因 DNS 限制)。

      • Microsoft Internet Explorer:
        URL 最大長度為 2,083 個字元。

    • 無法被處理的字元

        以及,不同的編碼方法 (Method) 所不支援的字元,需要特別留意錯誤處理的機制,如:

      • UTF-16 的 High (Leading) Surrogate 字元,範圍從 U+D800 到 U+DBFF。

    • 重複編碼

        最後,對於 %HEXDIG HEXDIG 序列依照不同的使用情境上可區分為:

      • 需要重複編碼
      • 不需要重複編碼

     

  4. 4. .NET 平臺常見的 URL 編碼方法

      以下簡單列出一些自己在 .NET 平臺中常見的 URL 編碼方法,我們也會先針對這些方法繼續接下來的探討。

    • .NET Framework 4.7.2

      • System.Web.HttpUtility.UrlEncode(String)

      • System.Net.WebUtility.UrlEncode(String)

      • System.Uri.EscapeDataString(String)

      • System.Uri.EscapeString(String)

    • .NET Platform Extensions 2.1

      • System.Text.Encodings.Web.UrlEncoder.Encode(String)

    • Flurl

      • Flurl.Url.Encode(String, Boolean)

      • Flurl.Url.EncodeIllegalCharacters(String, Boolean)

     

  5. 5. 分析與比較

      接下來我們會對於上述常見的 URL 編碼方法進行分析與比較,讓我們能夠對於不同的使用情境下選擇適當的方法來進行 URL 編碼。

    • 透過單元測試進行測試

        我們先列出會對 URL 編碼方法的測試案例分類,其中期望的編碼結果皆為大寫的十六進位數字。

      項次 測試案例分類
      1 RFC 3986 保留字
      2 RFC 3986 未保留字
      3 空格字元編碼為 %20
      4 長度為 65,520 的字串
      5 任何的字元皆可接受(無拋出例外)
      6 不重複編碼百分號編碼的字串

        測試結果如下表所示,其中 表示全通過、× 表示無通過、 表示部分通過並加註通過率。

      測試案例分類 HttpUtility
      .UrlEncode
      WebUtility
      .UrlEncode
      Uri
      .Escape
      DataString
      Uri
      .Escape
      String
      UrlEncoder
      .Encode
      Flurl.Url
      .Encode
      Flurl.Url
      .Encode
      Illegal
      Characters
      RFC 3986 保留字 (27.78%) (77.78%) × (55.56%) ×
      RFC 3986 未保留字 (85.71%) (85.71%)
      空格字元編碼為 %20 × ×
      長度為 65,520 的字串 × × ×
      任何的字元皆可接受(無拋出例外) (98.44%) (98.44%) (98.44%) (98.44%)
      不重複編碼百分號編碼的字串 × × × × × ×

      ※ 期望結果皆為大寫十六進位數字。

       

    • 產生全字元編碼結果以便查找

        我們為了瞭解各個方法的編碼結果,也產生一份 CSV 檔案方便需要查找時使用。其中除了列出 URL 編碼方法外,還納入 HTML 與 JavaScript 編碼方法的結果,下面列出是其中一筆結果做為參考。

      Input Hexadecimal HttpUtility.UrlEncode HttpUtility.UrlEncodeUnicode HttpUtility.UrlPathEncode WebUtility.UrlEncode
      < 0x003C %3c %3c < %3C
      Uri.EscapeDataString Uri.EscapeUriString UrlEncoder.Encode Url.Encode Url.EncodeIllegalCharacters
      %3C %3C %3C %3C %3C
      HttpUtility.HtmlAttributeEncode HttpUtility.HtmlEncode HttpUtility.JavaScriptStringEncode WebUtility.HtmlEncode
      &lt; &lt; \u003c &lt;
      HtmlEncoder.Encode JavaScriptEncoder.Encode Sanitizer.GetSafeHtmlFragment
      &lt; \u003c &lt;

       

    • 小結

      • 非全部保留字有編碼,非全部未保留字無編碼,空格字元非為百分號編碼:

        • System.Web.HttpUtility.UrlEncode(String)
        • System.Net.WebUtility.UrlEncode(String)
      • 非全部保留字有編碼:

        • System.Text.Encodings.Web.UrlEncoder.Encode(String)
      • 無法處理 High Surrogate 字元,及長度大於 65,519 的字串:

        • System.Uri.EscapeDataString(String)
      • 僅編碼非法字元且無法處理 High Surrogate 字元,及長度大於 65,519 的字串:

        • System.Uri.EscapeString(String)
      • 無法處理 High Surrogate 字元:

        • Flurl.Url.Encode(String, Boolean)
      • 僅編碼非法字元且無法處理 High Surrogate 字元,並不重複編碼 %HEXDIG HEXDIG 的序列字串:

        • Flurl.Url.EncodeIllegalCharacters(String, Boolean)

     

  6. 6. 更好的解決方案?

      假設上述的方法都不適合使用情境的話,我們有沒有更好的方法?我們也可依照不同的使用情境來自行再開發編碼方法,例如:以 Flurl.Url.Encode 做為主要的編碼方法,若遇到 High Surrogate 字元則改用 UrlEncoder.Encode 編碼。

      自行再開發編碼方法的測試結果如下表所示:

    測試案例分類 自行開發編碼方法
    RFC 3986 保留字
    RFC 3986 未保留字
    空格字元編碼為 %20
    長度為 65,520 的字串
    任何的字元皆可接受(無拋出例外)
    不重複編碼百分號編碼的字串 ×

    ※ 期望結果皆為大寫十六進位數字。:全通過、×:無通過、:部分通過。

     

  7. 7. 結論

    • 我們瞭解到什麼是 URL Encoding,並知道需要編碼與不需要編碼的字元以及其他需要留意的問題,我們也對常見的方法進行分析比較。
    • 最後評估在我們的情境中除了自行開發編碼方法外,會優先選用 Flurl.URL.Encode/Decode,其次選用 System.Uri.EscapeDataString/ UnescapeDataString 進行 URL 的編解碼。
    • 完整的程式碼皆推送至個人 GitHub 中 UrlEncodingAnalysis 的儲存庫[14],歡迎有興趣的朋友參考與指教,非常感謝!

     

  8. 8. 參考資料

    1. RFC 3986 - Uniform Resource Identifier (URI): Generic Syntax
    2. 百分號編碼 - 維基百科,自由的百科全書
    3. W3C HTML 4.01 - 17.13.4.1 application/x-www-form-urlencoded
    4. RFC 2616 - Hypertext Transfer Protocol -- HTTP/1.1
    5. Maximum URL length is 2,083 characters in Internet Explorer
    6. UTF-16 - 維基百科,自由的百科全書
    7. HttpUtility Class (System.Web) | Microsoft Docs
    8. WebUtility Class (System.Net) | Microsoft Docs
    9. Uri Class (System) | Microsoft Docs
    10. UrlEncoder Class (System.Text.Encodings.Web) | Microsoft Docs
    11. Fluent URL Building – Flurl
    12. Better URL encoding/decoding · Issue #262 · tmenier/Flurl · GitHub
    13. asp.net - Server.UrlEncode vs. HttpUtility.UrlEncode - Stack Overflow
    14. Zhi-Wei/UrlEncodingAnalysis - GitHub

創用 CC 授權條款 本著作由Zhi-Wei製作,以創用CC 姓名標示-非商業性-相同方式分享 4.0 國際 授權條款釋出。