デコード

 ホームページなどのフォームから入力された日本語文字列をデコードしたいのです。けど、gnu-Smalltalkにはそんなメソッド用意されてないみたいっす。なので、Squeakの unescapePercentsWithTextEncoding: を参考にして(ぶっちゃけパクリました)作ってみた。

!String methodsFor: 'converting'!

urlDecode
    | inData str oldPos pos patt c asciiVal |

    str := ReadWriteStream on: ''.
    oldPos := 1.
    patt := '%([a-fA-F0-9][a-fA-F0-9])' asRegex.
    inData := self copyReplaceAll: '+' with: '%20'.

    [pos := (inData indexOfRegex: patt startingAt: oldPos 
        ifAbsent: [
            (oldPos <= inData size)
                ifTrue: [
                    str nextPutAll: (inData copyFrom: oldPos to: inData size).        
                    ^str contents
                ]
                ifFalse: [^str contents].
        ]) first. 
        pos > 0
    ]
    whileTrue: [
        str nextPutAll: (inData copyFrom: oldPos to: pos - 1).
        c := inData at: pos.
        (c = $% and: [pos + 2 <= inData size])
            ifTrue: [
                [c = $%] whileTrue: [
                    asciiVal := (inData at: pos+1) asUppercase digitValue * 16 +
                                (inData at: pos+2) asUppercase digitValue.

                    asciiVal > 255 ifTrue: [^self].
                        str nextPut: (Character value: asciiVal).

                    pos := pos + 3.
                    (pos <= inData size)
                         ifTrue: [ c := inData at: pos]
                         ifFalse: [c := nil].
                ].
                oldPos := pos.
            ]
            ifFalse: [
                str nextPut: c.
                oldPos := pos + 1
            ].
    ].
!

なんだかすごく読みづらいコードですね、ごめんなさい。
 んで、次の様なテストCGIで試してみた。

#!/usr/bin/env gst -Q

"testform.cgi"

| html inData |

html := 'Content-Type: text/html; charset=UTF-8

<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title>gst てすとふぉーむ</title>
</head>
<body>
<h1>gst てすとふぉーむ</h1>
<form action="testform.cgi" method="POST" accept-charset="UFT-8">
  <textarea name="text" rows="10" cols="40"></textarea><br>
  <input type="submit" value="入力">
</form>
<pre>
%1
</pre>
</body>
</html>
'.

((Smalltalk getenv: 'CONTENT_LENGTH') = nil)
    ifTrue: [stdout nextPutAll: html % {''}]
    ifFalse: [
        inData := ((stdin nextAvailable: 
            (Smalltalk getenv: 'CONTENT_LENGTH') asInteger) tokenize: '=') at: 2.
        stdout nextPutAll: html % {inData urlDecode}
    ].
!

 環境に合わせて charset をEUC-JP、Shift_JISUTF-8 の三つで試してみましたが、問題無くデコードできました。それよりも問題なのは、 stdin でした。最初はローカル内のapache2系を使って stdin next: というふうに読み出していて、特になんの問題も無かったので、自分が借りてるレンタルサーバーで使われているapache1.3系でテストしてみると、何故かエラーです。エラーの詳細を調べてみると、1023バイト以上のPOSTデータを正しく読み出せていないのが原因でした。何故こんなエラーが出るのか解からないまま、とりあえず、試しに stdin contents としてみたらちゃんと読み出せた。けど、この方法だと今度はapache2系でnilエラーが出るという摩訶不思議な現象。色々試した結果、stdin nextAvailable:だとどのバージョンのapacheでもちゃんと読み出せるようなので、とりあえずOK.(いいのか?)