URL編碼與解碼原理

YunSoul發表於2018-08-22

一、概述

在開始討論編碼解碼之前,首先來明確一下問題。 什麼是application/x-www-form-urlencoded字串? 答:它是一種編碼型別。當URL地址裡包含非西歐字元的字串時,系統會將這些字元轉換成application/x-www-form-urlencoded字串。

表單裡提交時也是如此,當包含非西歐字元的字串時,系統也會將這些字元轉換成application/x-www-form-urlencoded字串。

然而,在向伺服器傳送大量的文字、包含非ASCII字元的文字或二進位制資料時這種編碼方式效率很低。這個時候我們就要使用另一種

編碼型別“multipart/form-data”,比如在我們在做上傳的時候,表單的enctype屬性一般會設定成“multipart/form-data”。

Browser端

表單的ENCTYPE屬性值為multipart/form-data,它告訴我們傳輸的資料要用到多媒體傳輸協議,由於多媒體傳輸的都是大量的資料,所以規 定上傳檔案必須是post方法,的type屬性必須是file。

二、Java URL編碼解碼API

該方法要求你自己指定編碼形式。這兩個類都不用初始化:  

public class URLDecoder extends Object
  public class URLEncoder extends Object
String   text1  =   java.net.URLEncoder.encode("中國",   "utf-8");   
        String   text2   =   java.net.URLDecoder.decode(text1,   "utf-8");
複製程式碼

這兩條語句在同一個頁面中的話,得到的結果是:

text1:   %E4%B8%AD%E5%9B%BD     
  tex2:   中國  
  
  String  China=new      String(request.getParameter("China").getBytes("iso8859_1"));   
  China=java.net.URLDecoder.decode(zhongguo,"utf-8");
複製程式碼

1、URLEncoder

java提供了一個類URLEncoder把string編碼成這種形式。Java1.2增加了一個類URLDecoder它能以這種形式解碼string。這個方法之前總是用它所在平臺的預設編碼形式,所以在不同系統上,它就會產生不同的結果。但是在java1.4中,這個方法被另一種方法取代了。 特別需要注意的是這個方法編碼了符號,“\” ,“&”,“=”,和“:”,並把空格(“ ”)轉換成了(+)。它不會嘗試著去規定在一個URL中這些字元怎樣被使用。由此,你不得不分塊編碼你的URL,而不是把整個URL一次傳給這個方法。這是很重要的,因為對類URLEncoder最通常的用法就是查詢string,為了和伺服器端使用GET方式的程式進行互動。 例如,假設你想編碼這個string:   

pg=q&kl=XX&stype=stext&q=+"Java+I/O"&search.x=38&search.y=3
複製程式碼

  這段程式碼對其進行編碼:   

String query = java.net.URLEncoder.encode( "pg=q&kl=XX&stype=stext&q=+"Java+I/O"&search.x=38&search.y=3");
       System.out.println(query);
複製程式碼

  不幸的是,得到的輸出是:   

pg%3Dq%26kl%3DXX%26stype%3Dstext%26q%3D%2B%22Java%2BI%2FO%22%26search.x%3D38%26search.y%3D3
複製程式碼

  出現這個問題就是方法URLEncoder.encode( ) 在進行盲目地編碼。它不能區分在URL或者查詢string中被用到的特殊字元(像前面string中的“=”,和“&”)和確實需要被編碼的字元。所以URL需要一次只編碼一塊。

2、URLDecoder

與URLEncoder 類相對應的URLDecoder 類有兩種靜態方法。它們解碼以x-www-form-url-encoded這種形式編碼的string。也就是說,它們把所有的加號(+)轉換成空格符,把所有的%xx分別轉換成與之相對應的字元:

public static String decode(String s) throws Exception    
    public static String decode(String s, String encoding) // Java 1.4 throws UnsupportedEncodingException
複製程式碼

如果string包含了一個“%”,但緊跟其後的不是兩位16進位制的數或者被解碼成非法序列,該方法就會丟擲IllegalArgumentException 異常。當下次再出現這種情況時,它可能就不會被丟擲了。這是與執行環境相關的,當檢查到有非法序列時,拋不丟擲IllegalArgumentException 異常,這時到底會發生什麼是不確定的。在Sun's JDK 1.4中,不會丟擲什麼異常,它會把一些莫名其妙的位元組加進不能被順利編碼的string中。這的確令人頭疼,可能就是一個安全漏洞。

由於這個方法沒有觸及到非轉義字元,所以你可以把整個URL作為引數傳給該方法<如下面的qerry>。不用像之前那樣分塊進行,依然可以得到你想要的正確的解碼結果。例如:

String input = "http://www.altavista.com/cgi-bin/"+"qerry?pg=q&kl=XX&stype=stext&q=%2B%22Java+I%2FO%22&search.x=38&search.y=3";&emsp;&emsp;  
try {&emsp;&emsp;  
    String output = java.net.URLDecoder.decode(input, "UTF-8");&emsp;&emsp;  
    System.out.println(output);&emsp;  
&emsp;}
複製程式碼

通常如果一樣東西需要編碼,說明這樣東西並不適合傳輸。原因多種多樣,如Size過大,包含隱私資料,對於Url來說,之所以要進行編碼,是因為Url中有些字元會引起歧義。

例如,Url引數字串中使用key=value鍵值對這樣的形式來傳參,鍵值對之間以&符號分隔,如/s?q=abc&ie=utf-8。如果你的value字串中包含了=或者&,那麼勢必會造成接收Url的伺服器解析錯誤,因此必須將引起歧義的&和=符號進行轉義,也就是對其進行編碼。

又如,Url的編碼格式採用的是ASCII碼,而不是Unicode,這也就是說你不能在Url中包含任何非ASCII字元,例如中文。否則如果客戶端瀏覽器和服務端瀏覽器支援的字符集不同的情況下,中文可能會造成問題。

Url編碼的原則:就是使用安全的字元(沒有特殊用途或者特殊意義的可列印字元)去表示那些不安全的字元。   預備知識:URI是統一資源標識的意思,通常我們所說的URL只是URI的一種。典型URL的格式如下所示。下面提到的URL編碼,實際上應該指的是URI編碼。

foo://example.com:8042/over/there?name=ferret#nose
   \_/ \______________/ \________/\_________/ \__/
    |         |              |         |        |
  scheme     authority                path      query   fragment
複製程式碼
  哪些字元需要編碼

  RFC3986文件規定,Url中只允許包含英文字母(a-zA-Z)、數字(0-9)、-_.~4個特殊字元以及所有保留字元。RFC3986文件對Url的編解碼問題做出了詳細的建議,指出了哪些字元需要被編碼才不會引起Url語義的轉變,以及對為什麼這些字元需要編碼做出了相應的解釋。         US-ASCII字符集中沒有對應的可列印字元:Url中只允許使用可列印字元。US-ASCII碼中的10-7F位元組全都表示控制字元,這些字元都不能直接出現在Url中。同時,對於80-FF位元組(ISO-8859-1),由於已經超出了US-ACII定義的位元組範圍,因此也不可以放在Url中。      保留字元:Url可以劃分成若干個元件,協議、主機、路徑等。有一些字元(:/?#[]@)是用作分隔不同元件的。例如:冒號用於分隔協議和主機,/用於分隔主機和路徑,?用於分隔路徑和查詢引數,等等。還有一些字元(!$&'()*+,;=)用於在每個元件中起到分隔作用的,如=用於表示查詢引數中的鍵值對,&符號用於分隔查詢多個鍵值對。 當元件中的普通資料包含這些特殊字元時,需要對其進行編碼。

RFC3986中指定了以下字元為==保留字元:! * ' ( ) ; : @ & = + $ , / ? # [ ]==

不安全字元:還有一些字元,當他們直接放在Url中的時候,可能會引起解析程式的歧義。這些字元被視為不安全字元,原因有很多。

  • 空格:Url在傳輸的過程,或者使用者在排版的過程,或者文字處理程式在處理Url的過程,都有可能引入無關緊要的空格,或者將那些有意義的空格給去掉。
  • 引號以及<>:引號和尖括號通常用於在普通文字中起到分隔Url的作用
  • #:通常用於表示書籤或者錨點
  • %:百分號本身用作對不安全字元進行編碼時使用的特殊字元,因此本身需要編碼
  • {}|^[]`~:某一些閘道器或者傳輸代理會篡改這些字元   

需要注意的是,對於Url中的合法字元,編碼和不編碼是等價的,但是對於上面提到的這些字元,如果不經過編碼,那麼它們有可能會造成Url語義的不同。因此對於Url而言,只有普通英文字元和數字,特殊字元$-_.+!*'()還有保留字元,才能出現在未經編碼的Url之中。其他字元均需要經過編碼之後才能出現在Url中。   

但是由於歷史原因,目前尚存在一些不標準的編碼實現。例如對於~符號,雖然RFC3986文件規定,對於波浪符號~,不需要進行Url編碼,但是還是有很多老的閘道器或者傳輸代理會進行編碼。

  如何對Url中的非法字元進行編碼

     Url編碼通常也被稱為百分號編碼(Url Encoding,also known as percent-encoding),是因為它的編碼方式非常簡單,使用%百分號加上兩位的字元——0123456789ABCDEF——代表一個位元組的十六進位制形式。Url編碼預設使用的字符集是US-ASCII。例如a在US-ASCII碼中對應的位元組是0x61,那麼Url編碼之後得到的就是%61,我們在位址列上輸入http://g.cn/search?q=%61%62%63,實際上就等同於在google上搜尋abc了。又如@符號在ASCII字符集中對應的位元組為0x40,經過Url編碼之後得到的是%40。      對於非ASCII字元,需要使用ASCII字符集的超集進行編碼得到相應的位元組,然後對每個位元組執行百分號編碼。對於Unicode字元,RFC文件建議使用utf-8對其進行編碼得到相應的位元組,然後對每個位元組執行百分號編碼。如"中文"使用UTF-8字符集得到的位元組為0xE4 0xB8 0xAD 0xE6 0x96 0x87,經過Url編碼之後得到"%E4%B8%AD%E6%96%87"。      如果某個位元組對應著ASCII字符集中的某個非保留字元,則此位元組無需使用百分號表示。例如"Url編碼",使用UTF-8編碼得到的位元組是0x55 0x72 0x6C 0xE7 0xBC 0x96 0xE7 0xA0 0x81,由於前三個位元組對應著ASCII中的非保留字元"Url",因此這三個位元組可以用非保留字元"Url"表示。最終的Url編碼可以簡化成"Url%E7%BC%96%E7%A0%81" ,當然,如果你用"%55%72%6C%E7%BC%96%E7%A0%81"也是可以的。

很多HTTP監視工具或者瀏覽器位址列等在顯示Url的時候會自動將Url進行一次解碼(使用UTF-8字符集),這就是為什麼當你在Firefox中訪問Google搜尋中文的時候,位址列顯示的Url包含中文的緣故。但實際上傳送給服務端的原始Url還是經過編碼的。


#### YunSoul技術分享,掃碼關注微信公眾號##
    ——只要你學會了之前所不會的東西,只要今天的你強過了昨天的你,那你就一直是在進階的路上了。 URL編碼與解碼原理

相關文章