Gauche で OAuth

Twitter が OAuth という認証手続きを採用したそうだ。 現在の Basic 認証は近く廃止予定だそうで、 Twitter 関連ソフトは OAuth 対応を余儀なくされている。 OAuth についてとりあげているブログ記事もちらほらと見掛ける。
流行に乗ろうというわけでもないが、 Gauche で OAuth 認証 (コンシューマ側) のコードを書いてみた。 oauth_token とかを取得するあたりまでだけだが、署名の部分だけ使いまわせばあとは特に面倒なこともないだろう。
コンシューマキーやコンシューマ秘密鍵は (当然だが) 各自で取得して欲しい。

(use rfc.http)
(use rfc.sha)
(use rfc.hmac)
(use rfc.base64)
(use www.cgi)
(use math.mt-random)
(use gauche.uvector)

(define consumer-key "ここにコンシューマキー")
(define consumer-secret "ここにコンシューマ秘密鍵")

(define (uri-encode-string str)
  (call-with-string-io str
    (lambda(in out)
      (while (read-char in) (compose not eof-object?) => ch
             (if (char-set-contains? #[a-zA-Z0-9.~_-] ch)
                 (write-char ch out)
                 (format out "%~2,'0X" (char->integer ch)))))))

(define (time-stamp)
  (number->string (sys-time)))

(define (random-string)
  (let ((random-source
         (make <mersenne-twister> :seed (sys-time)))
        (v (make-u32vector 10)))
    (mt-random-fill-u32vector! random-source v)
    (digest-hexify (sha1-digest-string (x->string v)))))

(define (query-compose query)
  (string-join (map (cut string-join <> "=") query) "&"))

(define (signature method uri info :optional (token-secret ""))
  (let* ((query-string (query-compose info))
         (signature-basic-string
          (string-append method "&"
                         (uri-encode-string uri) "&"
                         (uri-encode-string query-string))))
    (uri-encode-string
     (base64-encode-string
      (hmac-digest-string signature-basic-string
                          :key #`",|consumer-secret|&,|token-secret|"
                          :hasher <sha1>)))))

(define query
  `(("oauth_consumer_key" ,consumer-key)
    ("oauth_nonce" ,(random-string))
    ("oauth_signature_method" "HMAC-SHA1")
    ("oauth_timestamp" ,(time-stamp))
    ("oauth_version" "1.0")))

(write
 (let1 s (signature "POST" "http://twitter.com/oauth/request_token" query)
   (receive (status header body)
       (http-post "twitter.com"
                  "/oauth/request_token"
                  (query-compose
                   `(,@query ("oauth_signature" ,s))))
     (cgi-parse-parameters :query-string body))))

特に注意が必要なのは Gaucheuri-encode-string をそのまま使っては駄目だということだ。 Gaucheuri-encode-string はエンコード結果の16進数を小文字で出力するが、 OAuth はこのとき大文字であることを期待する。
URI について定めている RFC3986 では大文字でも小文字でもかまわないことになっているので、他の言語 (ライブラリ) でもそういうものがあるかもしれない。 これにハマってずいぶんと時間を浪費してしまった。
Document ID: 9b2264f132bc89fb999faf671784d6c1