Gauche:メール
メールを扱うためのコードを書きはじめました。 こんな感じでいいんでしょうか。satoru
- Shiro ようこそ。メール関係はやりかけで放ってあったので (中途半端なrfc.822とか)、これはありがたいです。(2002/09/10 00:13:29 PDT)
ヘッダのフィールドを復号するコード
?Q? な quoted-printable には対応していません。rfc.quopri が欲しいかも。
- Shiro 実はundocumentedなrfc.quoted-printableというモジュールが
もうずいぶん前のリリースから存在しています。
なぜundocumentedかというと、RFC2045の全てのケースにまだ対応してなかったから
だったと思うのですが、何を対応していなかったのか忘れてしまいました。
良ければ使ってみてください。
- satoruありゃりゃ。確かにありますね。気づきませんでした。 しかし、ちょっと試そうと思って次のように実行したら無限ループにはまりました。
% gosh -u rfc.quoted-printable (quoted-printable-encode-string "abc")
- Shiro メイル送信に関しては、rfc.smtpモジュールとかで低レベルの プロトコルをカバーして、mailみたいな高レベルのモジュールでいくつかの バックエンド(SMTP直接通信とか、sendmailを叩くとか)に対応しようかとぼんやり 考えていたのですが、いつまで立っても出来そうにないので とりあえず使えそうなものが出来たらバンドルしたいです。
(use gauche.regexp) (use gauche.charconv) (use rfc.base64) (use rfc.quoted-printable)
;; メールのヘッダのフィールドを復号する関数 ;; (decode-field "=?iso-2022-jp?B?GyRCJCIbKEI=?=\n abc" "eucjp") => "あabc" (define (decode-field str to-code) (with-error-handler (lambda (e) str) (lambda () ;; only `B' encoding is supported. (regexp-replace-all #/=\?([^?]+)\?([BQ])\?([^?]+)\?=\s*/ str (lambda (m) (let* ((charcode (rxmatch-substring m 1)) (encoding (rxmatch-substring m 2)) (message (rxmatch-substring m 3)) (decode (if (equal? encoding "B") base64-decode-string quoted-printable-decode-string))) (ces-convert (decode message) charcode to-code)))))))
- skimu charcode が例から切り抜いた iso-2022-jp だと ces-convert がエラーで おわります。 しかし error handler にこうゆう使い方があったとは。すごい。
- satoru エラー処理のまわりはよくわかってないです。Ruby だと例外が細かく分類されていて、特定の例外だけ捕まえて他は無視するといったようなことができますが、Gaucheだとこの辺はどうなんでしょう。Guile でも例外が分類されていて (catch 'system-error ...) のように特定の例外だけを捕まえられるみたいです。やったことないですけど。
- Shiro いずれ例外を細かくクラス分けしたいんですが、まだきちんと考えてないのです。JavaかRubyのを参考にしようかな。
- nfunato こことかここにある Dylanのとか如何でしょう? 別にDylan教ではないのですが、すっきりしていて好きでした。もっとも、srfiの仕様とか知らなくって、continuationとの相互作用まではわからないんですが…
メールを送信するコード
;; (send-mail "localhost" 25 (open-input-string "Hello!") ;; "from@localhost" "to@localhost") (define (send-mail host port iport mail-from recipients) (with-error-handler (lambda (e) (errorf "send-mail failed: ~a" (slot-ref e 'message))) (lambda () (call-with-client-socket (make-client-socket 'inet host port) (lambda (in out) (let ((send-command (lambda (command code) (when command (format out "~a\r\n" command) (flush out)) (let* ((line (read-line in)) (return-code (string->number (substring line 0 3)))) (if (eq? return-code code) line (errorf "smtp-error: ~a => ~a" command line)))))) (send-command #f 220) (send-command (format "HELO ~a" (sys-gethostname)) 250) (send-command (format "MAIL FROM: <~a>" mail-from) 250) (for-each (lambda (rcpt) (send-command (format "RCPT TO: <~a>" rcpt) 250)) (if (string? recipients) (list recipients) recipients)) (send-command "DATA" 354) (port-for-each (lambda (line) (if (equal? "." line) (format out "..\r\n") (format out "~a\r\n" line))) (lambda () (read-line iport))) (send-command "." 250) (send-command "QUIT" 221)))))))
satoru さすがに HELO localhost 決め打ちは強引かも。 ローカルホストのホスト名を取得するにはどうすればいいのでしょう。 Ruby だと ruby -rsocket -e 'puts Socket.gethostname' でいけるんですが、 Gauche のやり方をパッと調べられなかったのでここで質問してしまいます。 あと、 rcpt-to はリストを受け取れるようにした方がいいかな。 1回のsmtpセッションで複数のメールを送れるように、 とか言い始めると複雑になりますね。
- Shiro sys-gethostnameというのがあります。 「あの機能はどこに?」という時は、 (apropos 'hostname)とか、 grep gauche-ref.texiとか、あとinfo gauche-refjでAppendix "CとScheme関数の対応" も役に立つかもしれません。
- nobsun 同報メールなら、宛先リスト rcpt-to-list が与えられたとして、当該部分は下のものと置き換えればよさそう。
(for-each (lambda (r) (send-command (format "RCPT TO: <~a>" r) 250) rcpt-to-list)
- satoru リストを受け取るようにしました。が、宛先が1つのときでもリストにするのは面倒なので、宛先が1つのときは文字列でも渡せるようにしてみました。こういうAPIってどうなんでしょうね。ちょっとすっきりしないかも。 . recipients にしようかとちょっと迷ったけど、 . recipients だと、0個の宛先を防がないといけないし、宛先リストを渡したいときに不便だし (すべての引数をリストにまとめて (apply send-mail args) しないといけない?)、というわけでやめました。
- satoru nobsun と同時に編集していて、直前の修正が消されちゃいました。Wikiの同時編集問題ってみなさんどうやって解決しているのかしら。
- Shiro あれ、警告出ませんでした? 今は秒単位のタイムスタンプで上書きを判断しているので、秒以下のオーダーで競合が入ると上書きしてしまいますが…
- nobsun あっ。ひょっとして、警告を無視したような。気がします。ゴメンナサイ。自分のマシンで一人でつかっているときでも、警告がでることがあるので、反射的に無視する癖がついてしまってます。以後、気をつけます。
- Shiro ブラウザのbackボタンで編集画面に戻って再コミットしたり、 コミット後の画面でリロードをかけると警告が出ます。というのは、編集画面にhiddenでそのページの最終更新時が記録されてて、コミットする際にデータベースの更新時と比較しているからです。
- baal コード使わせていただいてます。 Gauche-0.8.11 ではなぜかうまく動かなかったのでちょっと修正しました。 (flush out) のとこです。