Gauche:メール

Gauche:メール

メールを扱うためのコードを書きはじめました。 こんな感じでいいんでしょうか。satoru

ヘッダのフィールドを復号するコード

?Q? な quoted-printable には対応していません。rfc.quopri が欲しいかも。

      % gosh -u rfc.quoted-printable
      (quoted-printable-encode-string "abc")
  (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)))))))

メールを送信するコード

  ;; (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セッションで複数のメールを送れるように、 とか言い始めると複雑になりますね。

             (for-each (lambda (r) (send-command (format "RCPT TO: <~a>" r) 250) rcpt-to-list)
More ...