1. 程式人生 > >golang 原始碼分析之URL編碼規範

golang 原始碼分析之URL編碼規範

首先看一下url編碼規範:

      backspace      %08
      tab            %09
      linefeed       %0A
      creturn        %0D
      space          %20
      !              %21
      "              %22
      #              %23
      $              %24
      %              %25
      &              %26
      '              %27
( %28 ) %29 * %2A + %2B , %2C - %2D . %2E / %2F 0 %30 1 %31 2 %32 3 %33 4 %34
5 %35 6 %36 7 %37 8 %38 9 %39 : %3A ; %3B < %3C = %3D > %3E ? %3F @ %40
A %41 B %42 C %43 D %44 E %45 F %46 G %47 H %48 I %49 J %4A K %4B L %4C M %4D N %4E O %4F P %50 Q %51 R %52 S %53 T %54 U %55 V %56 W %57 X %58 Y %59 Z %5A [ %5B \ %5C ] %5D ^ %5E _ %5F ` %60 a %61 b %62 c %63 d %64 e %65 f %66 g %67 h %68 i %69 j %6A k %6B l %6C m %6D n %6E o %6F p %70 q %71 r %72 s %73 t %74 u %75 v %76 w %77 x %78 y %79 z %7A { %7B | %7C } %7D ~ %7E ¢ %A2 £ %A3 ¥ %A5 | %A6 § %A7 « %AB ¬ %AC ¯ %AD º %B0 ± %B1 ª %B2 , %B4 µ %B5 » %BB ¼ %BC ½ %BD ¿ %BF À %C0 Á %C1 Â %C2 Ã %C3 Ä %C4 Å %C5 Æ %C6 Ç %C7 È %C8 É %C9 Ê %CA Ë %CB Ì %CC Í %CD Î %CE Ï %CF Ð %D0 Ñ %D1 Ò %D2 Ó %D3 Ô %D4 Õ %D5 Ö %D6 Ø %D8 Ù %D9 Ú %DA Û %DB Ü %DC Ý %DD Þ %DE ß %DF à %E0 á %E1 â %E2 ã %E3 ä %E4 å %E5 æ %E6 ç %E7 è %E8 é %E9 ê %EA ë %EB ì %EC í %ED î %EE ï %EF ð %F0 ñ %F1 ò %F2 ó %F3 ô %F4 õ %F5 ö %F6 ÷ %F7 ø %F8 ù %F9 ú %FA û %FB ü %FC ý %FD þ %FE ÿ %FF

下面以golang的編碼舉例

    t := &url.URL{Path: "www.abc.com/abc/*/abd?a=1&b=\"eee\""}
    fmt.Println(t.String())
    fmt.Println(url.QueryEscape("www.abc.com/abc/*/abd?a=1&b=\"eee\""))

結果如下:
www.abc.com/abc/%2A/abd%3Fa=1&b=%22eee%22
www.abc.com%2Fabc%2F%2A%2Fabd%3Fa%3D1%26b%3D%22eee%22
如果是String方法編碼時會略過/ & =等字元,而QueryEscape則是全部轉義。首先看golang url stirng方法的實現/usr/local/go/src/net/url/url.go

func (u *URL) String() string {
...
//先解析協議和host,然後解析path
path := u.EscapedPath()
...
}

具體實現

func (u *URL) EscapedPath() string {
    if u.RawPath != "" && validEncodedPath(u.RawPath) {
        p, err := unescape(u.RawPath, encodePath)
        if err == nil && p == u.Path {
            return u.RawPath
        }
    }
    if u.Path == "*" {
        return "*" // don't escape (Issue 11202)
    }
    return escape(u.Path, encodePath)
}

通過validEncodedPath判斷是否是一個規範的url通過下面的escape去編碼

func escape(s string, mode encoding) string {
    spaceCount, hexCount := 0, 0
    for i := 0; i < len(s); i++ {
        c := s[i]
        if shouldEscape(c, mode) {
            if c == ' ' && mode == encodeQueryComponent {
                spaceCount++
            } else {
                hexCount++
            }
        }
    }

    if spaceCount == 0 && hexCount == 0 {
        return s
    }

    t := make([]byte, len(s)+2*hexCount)
    j := 0
    for i := 0; i < len(s); i++ {
        switch c := s[i]; {
        case c == ' ' && mode == encodeQueryComponent:
            t[j] = '+'
            j++
        case shouldEscape(c, mode):
            t[j] = '%'
            t[j+1] = "0123456789ABCDEF"[c>>4]
            t[j+2] = "0123456789ABCDEF"[c&15]
            j += 3
        default:
            t[j] = s[i]
            j++
        }
    }
    return string(t)
}

上面兩個地方需要解釋,第一個是shouldEscape判斷是否需要轉義

case '$', '&', '+', ',', '/', ':', ';', '=', '?', '@': // §2.2 Reserved characters (reserved)
        switch mode {
        case encodePath:
            return c == '?'

對於path的轉義,上面的字元是返回的是false,就是不轉義。
第二就是怎麼轉義escape裡面通過

            t[j] = '%'
            t[j+1] = "0123456789ABCDEF"[c>>4]
            t[j+2] = "0123456789ABCDEF"[c&15]

完成 *%2A 的轉義。

而對於QueryEscape來說

func QueryEscape(s string) string {
    return escape(s, encodeQueryComponent)

此時的mode為encodeQueryComponent
而此時shouldEscape中:

 case encodeQueryComponent: 
            return true

所以會對所有字元進行轉碼