URI.unescape
非ASCII入力の実装は壊れています。1.9.3バージョンは次のようになります。
def unescape(str, escaped = @regexp[:ESCAPED])
str.gsub(escaped) { [$&[1, 2].hex].pack('C') }.force_encoding(str.encoding)
end
使用中の正規表現はです/%[a-fA-F\d]{2}/
。したがって、文字列を調べて、パーセント記号とそれに続く2桁の16進数を探します。ブロック内$&
には、一致したテキスト(たとえば、「%C3」)があり$&[1,2]
、先頭のパーセント記号('C3'
)がない一致したテキストになります。次にString#hex
、その16進数をFixnum(195
)に変換し、配列()でラップして、バイトマングリングを実行[195]
できるようにします。Array#pack
問題はpack
、1つのバイナリバイトが得られることです。
> puts [195].pack('C').encoding
ASCII-8BIT
ASCII-8BITエンコーディングは、「バイナリ」(つまり、特定のエンコーディングを持たないプレーンバイト)とも呼ばれます。次に、ブロックはそのバイトを返し、作業中のUTF-8エンコードされたコピーにString#gsub
挿入しようとすると、エラーが発生します。str
gsub
互換性のない文字エンコード:ASCII-8BITおよびUTF-8(Encoding :: CompositeError)
(一般的に)バイナリバイトを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エンコードされた文字列に混合し始めると、物事は崩壊し始めます。
簡単な修正の1つは、文字列をデコードする前に文字列をバイナリに切り替えることです。
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
失敗する場合と成功する場合がある理由はわかりません。