with-output-to-stringと文字コード

Common LispでWebアプリのお話。
POSTで日本語(UTF-8)を含んだ文字列を送信すると、文字化けしてしまったので、
原因を調べてみると、URLデコードを行っている関数が犯人のようでした。

(defun url-decode-string (s)
(with-output-to-string (out)
(do ((i 0 (1+ i)))
((>= i (length s)) out)
(let ((c (char s i)))
(case c
(#\% (setf c (code-char (parse-integer s
:start (+ i 1) :end (+ i 3)
:radix 16 :junk-allowed t)))
(unless (null c) (write-char c out))
(incf i 2))
(#\+ (write-char #\Space out))
(otherwise (write-char c out)))))))

outの文字コードがしていされていないのが原因だと思うんですけど、
with-output-to-stringでは:external-formatキーワードは指定できないようです。
ストリームを開いた後に文字コードをしている方法はないかと調べてみたら、
system::set-stream-external-formatなる関数があったのですが、
string-streamには使用できないみたいです。
色々苦心してみましたが結局解決策が見つからず、
一旦バイト列に入れてから文字列に変換することにしました。

;;; CLISP only
(defun url-decode-string2 (s)
(let ((arr (make-array (length s) :element-type '(mod 256))))
(do ((i 0 (1+ i))
(j 0 (1+ j)))
((>= i (length s))
(ext:convert-string-from-bytes arr charset:utf-8 :end j))
(let ((c (char s i)))
(case c
(#\% (setf c (parse-integer s
:start (+ i 1) :end (+ i 3)
:radix 16 :junk-allowed t))
(unless (null c) (setf (aref arr j) c))
(incf i 2))
(#\+ (setf (aref arr j) (char-code #\Space)))
(otherwise (setf (aref arr j) (char-code c))))))))

ext:convert-string-from-bytesという関数がバイト列を文字列に変換してくれます。
バイト列に文字コードを指定して文字列を直す関数があるのは非常に便利ですね。
残念ながらCL標準ではないようですが、大抵の処理系にはこの手の関数があるようです。

Leave a Reply