Update of /project/cl-smtp/cvsroot/cl-smtp In directory tiger.common-lisp.net:/tmp/cvs-serv20343
Modified Files: CHANGELOG cl-smtp.lisp tests.lisp Log Message: check message/html-message for non ascii characters, when found non ascii characters send messge/html-message encoded quoted-printable
--- /project/cl-smtp/cvsroot/cl-smtp/CHANGELOG 2010/09/08 15:02:32 1.20 +++ /project/cl-smtp/cvsroot/cl-smtp/CHANGELOG 2013/01/21 09:39:26 1.21 @@ -1,3 +1,10 @@ +Version 20130118 +2013.01.18 +Add string-has-non-ascii, rfc2045-q-encode-string-to-stream, +to send quoted-printable messages +Change write-rfc8822-message +Change cl-smtp.lisp, tests.lisp, CHANGELOG + Version 20100908.2 2010.09.08 Add write-rfc8822-message, to write a rfc8822 compatible mail. --- /project/cl-smtp/cvsroot/cl-smtp/cl-smtp.lisp 2010/09/08 15:02:32 1.20 +++ /project/cl-smtp/cvsroot/cl-smtp/cl-smtp.lisp 2013/01/21 09:39:26 1.21 @@ -56,10 +56,19 @@ (mask str)) resultstr))
-(defun string-to-base64-string (str) - (declare (ignorable str)) - #+allegro (excl:string-to-base64-string str :wrap-at-column nil) - #-allegro (cl-base64:string-to-base64-string str)) +(defun string-to-base64-string (str &key (external-format :utf-8) + (columns 80)) + (let ((exformat (flex:make-external-format external-format))) + #+allegro (excl:usb8-array-to-base64-string + (flex:string-to-octets str :external-format exformat) + :wrap-at-column columns) + #-allegro (cl-base64:usb8-array-to-base64-string + (flex:string-to-octets str :external-format exformat) + :columns columns))) + +(defun string-has-non-ascii (str) + (loop for c across str + when (< 127 (char-code c)) do (return t)))
(defun rfc2045-q-encode-string (str &key (external-format :utf-8)) (let ((line-has-non-ascii nil) @@ -75,7 +84,7 @@ (loop for byte across (flex:string-to-octets (make-string 1 :initial-element c) :external-format exformat) - do (format s "=~2,'0X" byte))) + do (format s "~:@(=~2,'0X~)" byte))) (t (when line-has-non-ascii (format s "?=") @@ -84,6 +93,42 @@ (when line-has-non-ascii (format s "?=")))))
+(defun rfc2045-q-encode-string-to-stream (str stream + &key (external-format :utf-8) + (columns 74)) + (let ((exformat (flex:make-external-format external-format)) + (last-line-break 0) + (len (length str))) + (loop for c across str + for n from 0 to len + for column = (- n last-line-break) + for nc = (when (< (+ n 1) len) (elt str (+ n 1))) + do + (when (>= column columns) + (write-char #= stream) + (write-blank-line stream) + (setf last-line-break n)) + (cond + ((char= c #\NewLine) + (setf last-line-break n) + (write-char c stream)) + ((or (char= c #\Space) + (char= c #\Tab)) + (if (char= nc #\NewLine) + (format stream "~:@(=~2,'0X~)" (char-code c)) + (write-char c stream))) + ((or (< 127 (char-code c)) + (> 33 (char-code c)) + (char= c #=)) + (loop for byte across (flex:string-to-octets + (make-string 1 :initial-element c) + :external-format exformat) + do (format stream "~:@(=~2,'0X~)" byte))) + (t + (write-char c stream)) + )) + )) + (defun substitute-return-newline (str) "Replace all occurences of \r\n in STR with spaces" (let ((resultstr "")) @@ -178,7 +223,8 @@ (defun send-email (host from to subject message &key ssl (port (if (eq :tls ssl) 465 25)) cc bcc reply-to extra-headers html-message display-name authentication - attachments (buffer-size 256) envelope-sender (external-format :utf-8)) + attachments (buffer-size 256) envelope-sender + (external-format :utf-8) local-hostname) (send-smtp host from (check-arg to "to") subject (mask-dot message) :port port :cc (check-arg cc "cc") :bcc (check-arg bcc "bcc") :reply-to reply-to @@ -192,7 +238,8 @@ 256) :envelope-sender (or envelope-sender from) :external-format external-format - :ssl ssl)) + :ssl ssl + :local-hostname (or local-hostname (usocket::get-host-name))))
(defun send-smtp (host from to subject message &key ssl (port (if (eq :tls ssl) 465 25)) cc bcc @@ -403,6 +450,9 @@ display-name attachments buffer-size (external-format :utf-8)) (let* ((boundary (make-random-boundary)) + (message-transfer-encoding (when (string-has-non-ascii message) + "quoted-printable")) + (html-boundary (if (and attachments html-message) (make-random-boundary) boundary)) @@ -415,7 +465,8 @@ :cc cc :reply-to reply-to :display-name display-name - :extra-headers extra-headers :subject subject) + :extra-headers extra-headers :subject subject + :external-format external-format) (when (or attachments html-message) (send-multipart-headers stream :attachment-boundary (when attachments boundary) @@ -436,31 +487,52 @@ (write-blank-line stream) (generate-message-header stream :boundary html-boundary :content-type content-type + :content-transfer-encoding message-transfer-encoding :content-disposition "inline" :include-blank-line? nil))) (attachments (generate-message-header stream :boundary boundary :content-type content-type :content-disposition "inline" + :content-transfer-encoding message-transfer-encoding :include-blank-line? nil)) (html-message (generate-message-header stream :boundary html-boundary :content-type content-type + :content-transfer-encoding message-transfer-encoding :content-disposition "inline")) (t - (generate-message-header stream :content-type content-type - :include-blank-line? nil))) + (generate-message-header + stream :content-type content-type + :content-transfer-encoding message-transfer-encoding + :include-blank-line? nil))) + (write-blank-line stream) + (if message-transfer-encoding + (progn + (print-debug (format nil "to server body quoted-printable: ~A" + message)) + (rfc2045-q-encode-string-to-stream message stream + :external-format external-format)) + (write-to-smtp stream message)) (write-blank-line stream) - (write-to-smtp stream message) (write-blank-line stream) ;;---------- Send Html text if needed ------------------------- (when html-message - (generate-message-header - stream :boundary html-boundary - :content-type (format nil "text/html; charset=~S" - (string-upcase (symbol-name external-format))) - :content-disposition "inline") - (write-to-smtp stream html-message) - (send-end-marker stream html-boundary)) + (let ((non-ascii-p (string-has-non-ascii html-message))) + (generate-message-header + stream :boundary html-boundary + :content-type (format nil "text/html; charset=~S" + (string-upcase (symbol-name external-format))) + :content-transfer-encoding (when non-ascii-p "quoted-printable") + :content-disposition "inline") + (if non-ascii-p + (progn + (print-debug + (format nil "to server html-message quoted-printable: ~A" + html-message)) + (rfc2045-q-encode-string-to-stream + html-message stream :external-format external-format)) + (write-to-smtp stream html-message)) + (send-end-marker stream html-boundary))) ;;---------- Send Attachments ----------------------------------- (when attachments (dolist (attachment attachments) --- /project/cl-smtp/cvsroot/cl-smtp/tests.lisp 2010/06/21 09:06:27 1.2 +++ /project/cl-smtp/cvsroot/cl-smtp/tests.lisp 2013/01/21 09:39:26 1.3 @@ -14,6 +14,51 @@ (defun get-component-pathname () (asdf:component-pathname (asdf:find-system "cl-smtp")))
+ + +(define-cl-smtp-test "rfc2045-q-encode-string-to-stream-1" () + (let* ((str "öüäÖÜÄß") + (qstr (with-output-to-string (s) + (rfc2045-q-encode-string-to-stream + str s :external-format :utf-8)))) + (assert qstr) + (assert (string-equal qstr "=C3=B6=C3=BC=C3=A4=C3=96=C3=9C=C3=84=C3=9F")))) + +(define-cl-smtp-test "rfc2045-q-encode-string-to-stream-2" () + (let* ((str "öüäÖÜÄß") + (qstr (with-output-to-string (s) + (rfc2045-q-encode-string-to-stream + str s :external-format :latin-1)))) + (assert qstr) + (assert (string-equal qstr "=F6=FC=E4=D6=DC=C4=DF")))) + +(define-cl-smtp-test "rfc2045-q-encode-string-to-stream-3" () + (let* ((str "check if #= encoded") + (qstr (with-output-to-string (s) + (rfc2045-q-encode-string-to-stream + str s :external-format :latin-1)))) + (assert qstr) + (assert (string-equal qstr "check if #=3D encoded")))) + +(define-cl-smtp-test "rfc2045-q-encode-string-to-stream-4" () + (let* ((str "Müde vom Durchwandern öder Letternwüsten, voll leerer Hirngeburten, in anmaaßendsten Wortnebeln ; überdrüssig ästhetischer Süßler wie grammatischer Wässerer ; entschloß ich mich : Alles, was je schrieb, in Liebe und Haß, als immerfort mitlebend zu behandeln !") + (qstr (with-output-to-string (s) + (rfc2045-q-encode-string-to-stream + str s :external-format :latin-1 :columns 64)))) + (assert qstr) + (assert (string-equal qstr "M=FCde vom Durchwandern =F6der Letternw=FCsten, voll leerer Hirngeburt= +en, in anmaa=DFendsten Wortnebeln ; =FCberdr=FCssig =E4sthetischer S=FC=DFle= +r wie grammatischer W=E4sserer ; entschlo=DF ich mich : Alles, was j= +e schrieb, in Liebe und Ha=DF, als immerfort mitlebend zu behandel= +n !" +)))) + +(define-cl-smtp-test "string-has-non-ascii-1" () + (assert (string-has-non-ascii "test Ü ende"))) + +(define-cl-smtp-test "string-has-non-ascii-2" () + (assert (not (string-has-non-ascii "test ende")))) + (define-cl-smtp-test "rfc2045-q-encode-string-utf-8" () (let* ((str "öüäÖÜÄß") (qstr (rfc2045-q-encode-string str :external-format :utf-8)))