| 1 | ;; -*- Lisp -*- |
|---|
| 2 | |
|---|
| 3 | ;; smtp-output-stream.lisp - Perform protocol and mail specific |
|---|
| 4 | ;; processing to convert an email message into an external format |
|---|
| 5 | ;; proper to be transfered through SMTP. |
|---|
| 6 | |
|---|
| 7 | ;; This stream type is used to do two things: |
|---|
| 8 | |
|---|
| 9 | ;; In the message header, convert all non-ASCII characters to their |
|---|
| 10 | ;; equivalent Q-encoded representation (RFC2047) |
|---|
| 11 | |
|---|
| 12 | ;; In the whole message, convert all line endings to CR+LF as required |
|---|
| 13 | ;; by the SMTP protocol. |
|---|
| 14 | |
|---|
| 15 | ;; This stream class should also perform automatic dot masking, tbd. |
|---|
| 16 | |
|---|
| 17 | (in-package :cl-smtp) |
|---|
| 18 | |
|---|
| 19 | (defclass smtp-output-stream (trivial-gray-stream-mixin fundamental-character-output-stream) |
|---|
| 20 | ((encapsulated-stream |
|---|
| 21 | :initarg :encapsulated-stream |
|---|
| 22 | :reader encapsulated-stream) |
|---|
| 23 | (in-header |
|---|
| 24 | :initform t |
|---|
| 25 | :accessor in-header |
|---|
| 26 | :documentation |
|---|
| 27 | "Currently emitting the header of the message") |
|---|
| 28 | (line-has-non-ascii |
|---|
| 29 | :initform nil |
|---|
| 30 | :accessor line-has-non-ascii |
|---|
| 31 | :documentation |
|---|
| 32 | "The current line has non ASCII characters in it") |
|---|
| 33 | (previous-char |
|---|
| 34 | :initform nil |
|---|
| 35 | :accessor previous-char |
|---|
| 36 | :documentation |
|---|
| 37 | "Previous character sent to the stream, used to detect end of header") |
|---|
| 38 | (external-format |
|---|
| 39 | :initform (flex:make-external-format :iso-8859-15) |
|---|
| 40 | :initarg :external-format |
|---|
| 41 | :reader external-format))) |
|---|
| 42 | |
|---|
| 43 | (defmethod stream-element-type ((stream smtp-output-stream)) |
|---|
| 44 | (stream-element-type (stream stream))) |
|---|
| 45 | |
|---|
| 46 | (defmethod close ((stream smtp-output-stream) &key abort) |
|---|
| 47 | (close (encapsulated-stream stream) :abort abort)) |
|---|
| 48 | |
|---|
| 49 | (defmethod stream-write-char ((stream smtp-output-stream) char) |
|---|
| 50 | (with-accessors ((in-header in-header) |
|---|
| 51 | (line-has-non-ascii line-has-non-ascii) |
|---|
| 52 | (previous-char previous-char) |
|---|
| 53 | (external-format external-format) |
|---|
| 54 | (encapsulated-stream encapsulated-stream)) stream |
|---|
| 55 | (when in-header |
|---|
| 56 | (cond |
|---|
| 57 | ;; Newline processing |
|---|
| 58 | ((eql char #\Newline) |
|---|
| 59 | ;; Finish quoting |
|---|
| 60 | (when line-has-non-ascii |
|---|
| 61 | (format encapsulated-stream "?=") |
|---|
| 62 | (setf line-has-non-ascii nil)) |
|---|
| 63 | ;; Test for end of header |
|---|
| 64 | (when (eql previous-char #\Newline) |
|---|
| 65 | (setf in-header nil))) |
|---|
| 66 | ((eql char #\Return) |
|---|
| 67 | ;; CR is suppressed here and added before each #\Newline |
|---|
| 68 | ) |
|---|
| 69 | ;; Handle non-ASCII characters |
|---|
| 70 | ((< 127 (char-code char)) |
|---|
| 71 | (unless line-has-non-ascii |
|---|
| 72 | (format encapsulated-stream "=?~A?Q?" (flex:external-format-name external-format)) |
|---|
| 73 | (setf line-has-non-ascii t)) |
|---|
| 74 | (loop for byte across (flex:string-to-octets (make-string 1 :initial-element char) |
|---|
| 75 | :external-format external-format) |
|---|
| 76 | do (format encapsulated-stream "=~2,'0X" byte)))) |
|---|
| 77 | (setf previous-char char)) |
|---|
| 78 | #+nil(when (eql char #\Newline) |
|---|
| 79 | (write-char #\Return encapsulated-stream)) |
|---|
| 80 | (unless (< 127 (char-code char)) |
|---|
| 81 | (write-char char encapsulated-stream)))) |
|---|
| 82 | |
|---|
| 83 | (defmethod stream-write-sequence ((stream smtp-output-stream) sequence start end &key) |
|---|
| 84 | (if (in-header stream) |
|---|
| 85 | (loop for i from start below end |
|---|
| 86 | do (stream-write-char stream (elt sequence i))) |
|---|
| 87 | (write-sequence sequence (encapsulated-stream stream) :start start :end end))) |
|---|