| 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))) |
|---|