的URI.unescape
的實施打破了非ASCII輸入。該1.9.3 version看起來是這樣的:
def unescape(str, escaped = @regexp[:ESCAPED])
str.gsub(escaped) { [$&[1, 2].hex].pack('C') }.force_encoding(str.encoding)
end
使用正則表達式是/%[a-fA-F\d]{2}/
。所以它通過字符串尋找一個百分號後跟兩個十六進制數字;在塊$&
將是匹配的文本(例如'%C3')並且$&[1,2]
是沒有前導百分號的匹配文本('C3'
)。然後,我們調用String#hex
將該十六進制數轉換爲Fixnum(195
)並將其包裝在Array([195]
)中,以便我們可以使用Array#pack
爲我們執行字節修改。的問題是,pack
給我們一個單一的二進制字節:
> puts [195].pack('C').encoding
ASCII-8BIT
的ASCII-8BIT編碼也被稱爲「二進制」(即純沒有特定的編碼字節)。然後塊返回字節和String#gsub
試圖插入的str
是gsub
的UTF-8編碼的複製工作,你會得到你的錯誤:
incompatible character encodings: ASCII-8BIT and UTF-8 (Encoding::CompatibilityError)
,因爲你不能(一般)只是把這些東西二進制字節轉換爲UTF-8字符串;你經常可以逃脫它:
URI.unescape("%C3%9F") # Works
URI.unescape("%C3µ") # Fails
URI.unescape("µ") # Works, but nothing to gsub here
URI.unescape("%C3%9Fµ") # Fails
URI.unescape("%C3%9Fpancakes") # Works
事情剛開始分崩離析一旦你開始非ASCII數據混合到您的URL編碼字符串。
一個簡單的解決方法是轉換字符串爲二進制之前嘗試將其解碼:
def unescape(str, escaped = @regexp[:ESCAPED])
encoding = str.encoding
str = str.dup.force_encoding('binary')
str.gsub(escaped) { [$&[1, 2].hex].pack('C') }.force_encoding(encoding)
end
另一種選擇是將force_encoding
推入塊:
def unescape(str, escaped = @regexp[:ESCAPED])
str.gsub(escaped) { [$&[1, 2].hex].pack('C').force_encoding(encoding) }
end
我不知道爲什麼gsub
在某些情況下失敗,但在其他情況下成功。
http://bugs.ruby-lang.org/ - 它是一個正確的地方報告標準ruby庫中的錯誤? – Bulwersator 2012-04-28 13:17:08
@Bulwersator:http://bugs.ruby-lang.org/projects/ruby/wiki/HowtoReport – 2012-04-28 18:15:16