root/trunk/thirdparty/cl-smtp/attachments.lisp

Revision 2782, 6.8 kB (checked in by hans, 5 months ago)

update cl-smtp from cvs

Line 
1 ;;; -*- mode: Lisp -*-
2        
3 ;;; This file is part of CL-SMTP, the Lisp SMTP Client
4
5
6 ;;; Copyright (C) 2004/2005/2006/2007 Jan Idzikowski
7
8 ;;; This library is free software; you can redistribute it and/or
9 ;;; modify it under the terms of the Lisp Lesser General Public License
10 ;;; (http://opensource.franz.com/preamble.html), known as the LLGPL.
11
12 ;;; This library is distributed in the hope that it will be useful,
13 ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
14 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 ;;; Lisp Lesser GNU General Public License for more details.
16
17 ;;; File: attachments.lisp
18 ;;; Description: encoding and transmitting login to include a mime attachment
19
20 ;;;
21 ;;; Contributed by Brian Sorg
22 ;;;
23 ;;; Thanks to David Cooper for make-random-boundary
24 ;;;
25 (in-package :cl-smtp)
26
27 ;;; Addition to allow for sending mime attachments along with the smtp message
28
29 ;;---- Initialize array of possible boundary characters to make start of attachments
30 (defparameter *boundary-chars*
31   (let* ((chars (list #\a #\b #\c #\d #\e #\f #\g #\h #\i #\j #\k #\l #\m
32                       #\n #\o #\p #\q #\r #\s #\t #\u #\v #\w #\x #\y #\z
33                       #\A #\B #\C #\D #\E #\F #\G #\H #\I #\J #\K #\L #\M
34                       #\N #\O #\P #\Q #\R #\S #\T #\U #\V #\W #\X #\Y #\Z
35                       #\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7 #\8 #\9))
36          (arr (make-array (length chars))))
37     (dotimes (i (length chars) arr)
38       (setf (aref arr i) (pop chars)))))
39
40 (defun make-random-boundary (&optional (length 30)
41                              (boundary-chars *boundary-chars*))
42   (let ((boundary (make-string length))
43         (prefix "_---------_")
44         (chars-length (length boundary-chars)))
45     (dotimes (i length (concatenate 'string prefix boundary))
46       (setf (aref boundary i)
47             (svref *boundary-chars* (random chars-length))))))
48
49 (defun generate-multipart-header (sock boundary &key (multipart-type "mixed"))
50   (write-to-smtp sock
51                  (format nil "Content-type: multipart/~a;~%~tBoundary=\"~a\""
52                          multipart-type boundary)))
53
54 (defun generate-message-header (sock
55                                 &key boundary ;; uniques string of character -- see make-random-boundary
56                                 content-type ;; "text/plain; charset=ISO-8859-1"
57                                 content-disposition ;; inline attachment
58                                 content-transfer-encoding ;; 7 bit or 8 bit
59                                 (include-blank-line? t))
60   (when boundary
61     (write-to-smtp sock (format nil "--~a" boundary)))
62   (when content-type
63     (write-to-smtp sock (format nil "Content-type: ~a" content-type)))
64   (when content-disposition
65     (write-to-smtp sock (format nil "Content-Disposition: ~A"
66                                 content-disposition)))
67   (when content-transfer-encoding
68     (write-to-smtp sock (format nil "Content-Transfer-Encoding: ~A"
69                                 content-transfer-encoding)))
70   (when include-blank-line? (write-blank-line sock)))
71
72 (defun send-attachment-header (sock boundary name)
73
74   (generate-message-header
75    sock
76    :boundary boundary
77    :content-type (format nil "~a;~%~tname=\"~a\"" (lookup-mime-type name) name)
78    :content-transfer-encoding "base64"
79    :content-disposition (format nil "attachment; filename=\"~a\"" name)))
80
81 (defun send-end-marker (sock boundary)
82   ;; Note the -- at beginning and end of boundary is required
83   (write-to-smtp sock (format nil "~%--~a--~%" boundary)))
84
85 (defun send-attachment (sock attachment boundary buffer-size)
86   (when (probe-file attachment)
87     (let ((name (file-namestring attachment)))
88       (send-attachment-header sock boundary name)
89       (base64-encode-file attachment sock :buffer-size buffer-size))))
90
91 (defun base64-encode-file (file-in sock
92                                    &key
93                                    (buffer-size 256) ;; in KB
94                                    (wrap-at-column 70))
95   "Encodes the file contents given by file-in, which can be of any form appropriate to with-open-file, and write the base-64 encoded version to sock, which is a socket.
96
97 Buffer-size, given in KB, controls how much of the file is processed and written to the socket at one time. A buffer-size of 0, processes the file all at once, regardless of its size. One will have to weigh the speed vs, memory consuption risks when chosing which way is best.
98
99 Wrap-at-column controls where the encode string is divided for line breaks."
100     (when (probe-file file-in)
101       ;;-- open filein ---------
102       (with-open-file (strm-in file-in
103                                :element-type '(unsigned-byte 8))
104         (let* ((;; convert buffer size given to bytes
105                 ;; or compute bytes based on file
106                 max-buffer-size
107                 (if (zerop buffer-size)
108                     (file-length strm-in)
109                     ;; Ensures 64 bit encoding is properly
110                     ;; divided so that filler
111                     ;; characters are not required between chunks
112                     (* 24 (truncate (/ (* buffer-size 1024) 24)))))
113                (column-count 0)
114                (eof? nil)
115                (buffer (make-array max-buffer-size
116                                    :element-type '(unsigned-byte 8))))
117           (loop
118            (print-debug (format nil "~%Process attachment ~a~%" file-in))
119            (let* ((;; read a portion of the file into the buffer arrary and
120                    ;; returns the index where it stopped
121                    byte-count (dotimes (i max-buffer-size max-buffer-size)
122                                 (let ((bchar (read-byte strm-in nil 'EOF)))
123                                   (if (eql bchar 'EOF)
124                                       (progn
125                                         (setq eof? t)
126                                         (return i))
127                                       (setf (aref buffer i) bchar))))))
128                   (if (zerop buffer-size)
129                       ;; send file all at once to socket.
130                       #+allegro
131                       (write-string (excl:usb8-array-to-base64-string
132                                      buffer wrap-at-column) sock)
133                       #-allegro
134                       (cl-base64:usb8-array-to-base64-stream
135                        buffer sock :columns wrap-at-column)
136                       ;; otherwise process file in chunks.
137                     ;; The extra encoded-string,
138                       ;; and its subseq functions are brute force methods
139                     ;; to properly handle the wrap-at-column feature
140                     ;; between buffers.
141                     ;; Not the most efficient way,
142                     ;; but it works and uses existing functions
143                     ;; in the cl-base64 package.
144                     (let* ((;; drops off extra elements that were not filled in in reading, this is important for lisp systems that default a value into
145                             ;; the array when it is created. -- ie Lispworks, SBCL
146                             trimmed-buffer (if eof?
147                                                (subseq buffer 0 byte-count)
148                                                buffer))
149                            (encoded-string
150                             #+allegro
151                              (excl:usb8-array-to-base64-string
152                               trimmed-buffer)
153                             #-allegro
154                             (cl-base64:usb8-array-to-base64-string
155                              trimmed-buffer)))
156                       (loop for ch across encoded-string
157                             do (progn
158                                  (write-char ch sock)
159                                  (incf column-count)
160                                  (when (= column-count wrap-at-column)
161                                    (setq column-count 0)
162                                    (write-char #\Newline sock))))))
163                   (force-output sock)
164                   (print-debug (format nil "~% Eof is ~a~%" eof?))
165                   (when (or (zerop buffer-size)
166                             eof?)
167                     (return))))))))
Note: See TracBrowser for help on using the browser.