comparison lisp/packages/feedmail.el @ 0:376386a54a3c r19-14

Import from CVS: tag r19-14
author cvs
date Mon, 13 Aug 2007 08:45:50 +0200
parents
children 34a5b81f86ba
comparison
equal deleted inserted replaced
-1:000000000000 0:376386a54a3c
1 ;;; feedmail.el --- outbound mail handling
2
3 ;; Keywords: mail
4
5 ;;; Synched up with: Not in FSF.
6
7 ;;; From: William.J.Carpenter@hos1cad.att.com (Bill C)
8 ;;; Subject: feedmail.el, patchlevel 2 [repost]
9 ;;; Date: 8 Jun 91 22:23:00 GMT
10 ;;; Organization: AT&T Bell Laboratories
11 ;;;
12 ;;; 5-may-92 jwz Conditionalized calling expand-mail-aliases, since that
13 ;;; function doesn't exist in Lucid GNU Emacs or when using
14 ;;; mail-abbrevs.el.
15 ;;;
16 ;;; Here's the latest version of feedmail.el, a replacement for parts of
17 ;;; GNUemacs' sendmail.el (specifically, it's what handles your outgoing
18 ;;; mail after you type C-c C-c in mail mode). (Sorry if you're seeing
19 ;;; this a second time. Looks like my earlier attempt to post it didn't
20 ;;; get off the local machine.)
21 ;;;
22 ;;; This version contains the following new things:
23 ;;;
24 ;;; * fix for handling default-case-fold-search
25 ;;; * involve user-full-name in default from line
26 ;;; * fix for my improper use of mail-strip-quoted-names when
27 ;;; addresses contain a mix of "<>" and "()" styles
28 ;;; * new feature allowing optional generation of Message-ID
29
30 ;;; feedmail.el
31 ;;; LCD record:
32 ;;; feedmail|Bill Carpenter|william.j.carpenter@att.com|Outbound mail handling|91-05-24|2|feedmail.el
33 ;;;
34 ;;; Written by Bill Carpenter <william.j.carpenter@att.com>
35 ;;; original, 31 March 1991
36 ;;; patchlevel 1, 5 April 1991
37 ;;; patchlevel 2, 24 May 1991
38 ;;;
39 ;;; As far as I'm concerned, anyone can do anything they want with
40 ;;; this specific piece of code. No warranty or promise of support is
41 ;;; offered.
42 ;;;
43 ;;; This stuff does in elisp the stuff that used to be done
44 ;;; by the separate program "fakemail" for processing outbound email.
45 ;;; In other words, it takes over after you hit "C-c C-c" in mail mode.
46 ;;; By appropriate setting of options, you can still use "fakemail",
47 ;;; or you can even revert to sendmail (which is not too popular
48 ;;; locally). See the variables at the top of the elisp for how to
49 ;;; achieve these effects:
50 ;;;
51 ;;; --- you can get one last look at the prepped outbound message and
52 ;;; be prompted for confirmation
53 ;;;
54 ;;; --- removes BCC: headers after getting address info
55 ;;;
56 ;;; --- does smart filling of TO: and CC: headers
57 ;;;
58 ;;; --- processes FCC: lines and removes them
59 ;;;
60 ;;; --- empty headers are removed
61 ;;;
62 ;;; --- can force FROM: or SENDER: line
63 ;;;
64 ;;; --- can generate a Message-ID line
65 ;;;
66 ;;; --- strips comments from address info (both "()" and "<>" are
67 ;;; handled via a call to mail-strip-quoted-names); the
68 ;;; comments are stripped in the simplified address list given
69 ;;; to a subprocess, not in the headers in the mail itself
70 ;;; (they are left unchanged, modulo smart filling)
71 ;;;
72 ;;; --- error info is pumped into a normal buffer instead of the
73 ;;; minibuffer
74 ;;;
75 ;;; --- just before the optional prompt for confirmation, lets you
76 ;;; run a hook on the prepped message and simplified address
77 ;;; list
78 ;;;
79 ;;; --- you can specify something other than /bin/mail for the
80 ;;; subprocess
81 ;;;
82 ;;; After a few options below, you will find the function
83 ;;; feedmail-send-it. Everything after that function is just local
84 ;;; stuff for this file. There are two ways you can use the stuff in
85 ;;; this file:
86 ;;;
87 ;;; (1) Put the contents of this file into sendmail.el and change the
88 ;;; name of feedmail-send-it to sendmail-send-it, replacing that
89 ;;; function in sendmail.el.
90 ;;;
91 ;;; or
92 ;;;
93 ;;; (2) Save this file as feedmail.el somewhere on your elisp
94 ;;; loadpath; byte-compile it. Put the following lines somewhere in
95 ;;; your ~/.emacs stuff:
96 ;;;
97 ;;; (setq send-mail-function 'feedmail-send-it)
98 ;;; (autoload 'feedmail-send-it "feedmail")
99 ;;;
100
101
102 (defvar feedmail-confirm-outgoing nil
103 "*If non-nil, gives a y-or-n confirmation prompt after prepping,
104 before sending mail.")
105
106
107 (defvar feedmail-nuke-bcc t
108 "*Non-nil means get rid of the BCC: lines from the message header
109 text before sending the mail. In any case, the BCC: lines do
110 participate in the composed address list. You probably want to keep
111 them if you're using sendmail (see feedmail-buffer-eating-function).")
112
113
114 (defvar feedmail-fill-to-cc t
115 "*Non-nil means do smart filling (line-wrapping) of TO: and CC: header
116 lines. If nil, the lines are left as-is. The filling is done after
117 mail address alias expansion.")
118
119
120 (defvar feedmail-fill-to-cc-fill-column default-fill-column
121 "*Fill column used when wrapping mail TO: and CC: lines.")
122
123
124 (defvar feedmail-nuke-empty-headers t
125 "*If non-nil, headers with no contents are removed from the outgoing
126 email. A completely empty SUBJECT: header is always removed,
127 regardless of the setting of this variable. The only time you would
128 want them left in would be if you used some headers whose presence
129 indicated something rather than their contents.")
130
131 ;;; wjc sez: I think the use of the SENDER: line is pretty pointless,
132 ;;; but I left it in to be compatible with sendmail.el and because
133 ;;; maybe some distant mail system needs it. Really, though, if you
134 ;;; want a sender line in your mail, just put one in there and don't
135 ;;; wait for feedmail to do it for you.
136
137 (defvar feedmail-sender-line nil
138 "*If nil, no SENDER: header is forced. If non-nil and the email
139 already has a FROM: header, a SENDER: header is forced with this as
140 its contents. You can probably leave this nil, but if you feel like
141 using it, a good value would be a fully-qualified domain name form of
142 your address. For example, william.j.carpenter@att.com. Don't
143 include a trailing newline or the keyword SENDER:. They're
144 automatically provided.")
145
146
147 ;; user-full-name suggested by kpc@ptolemy.arc.nasa.gov (=Kimball Collins)
148 (defvar feedmail-from-line
149 (concat (user-login-name) "@" (system-name) " (" (user-full-name) ")")
150 "*If non-nil and the email has no FROM: header, one will be forced
151 with this as its contents. A good value would be a fully-qualified
152 domain name form of your address. For example, william.j.carpenter@att.com.
153 (The default value of this variable is probably not very good, since
154 it doesn't have a domain part.) Don't include a trailing newline or
155 the keyword FROM:. They're automatically provided.")
156
157
158 ;;; Here's how I use the GNUS Message-ID generator for mail but not
159 ;;; for news postings:
160 ;;;
161 ;;; (setq feedmail-message-id-generator 'wjc:gnusish-message-id)
162 ;;; (setq gnus-your-domain "hos1cad.ATT.COM")
163 ;;;
164 ;;; (defun wjc:gnusish-message-id ()
165 ;;; (require 'gnuspost)
166 ;;; (if (fboundp 'wjc:gnus-inews-message-id)
167 ;;; (wjc:gnus-inews-message-id)
168 ;;; (gnus-inews-message-id)))
169 ;;;
170 ;;; (setq news-inews-hook
171 ;;; '(lambda ()
172 ;;; (defun gnus-inews-date () nil)
173 ;;; (fset 'wjc:gnus-inews-message-id (symbol-function 'gnus-inews-message-id))
174 ;;; (defun gnus-inews-message-id () nil)
175 ;;; ))
176 ;;;
177 (defvar feedmail-message-id-generator nil
178 "*If non-nil, should be a function (called with no arguments) which
179 will generate a unique message ID which will be inserted on a
180 Message-ID: header. The message ID should be the return value of the
181 function. Don't include trailing newline, leading space, or the
182 keyword MESSAGE-ID. They're automatically provided. Do include
183 surrounding <> brackets. For an example of a message ID generating
184 function, you could look at the GNUS function gnus-inews-message-id.
185 When called, the current buffer is the prepped outgoing mail buffer
186 (the function may inspect it, but shouldn't modify it). If the returned
187 value doesn't contain any non-whitespace characters, no message ID
188 header is generated, so you could generate them conditionally,
189 based on the contents of the mail.")
190
191
192 (defun feedmail-confirm-addresses-hook-example ()
193 "An example of a last chance hook that shows the simple addresses
194 and gets a confirmation. Use as (setq feedmail-last-chance-hook
195 'feedmail-confirm-addresses-hook-example)."
196 (save-window-excursion
197 (display-buffer feedmail-address-buffer)
198 (if (not (y-or-n-p "How do you like them apples? "))
199 (error "Sending...gave up in last chance hook"))))
200
201
202 (defvar feedmail-last-chance-hook nil
203 "*User's last opportunity to modify the message on its way out. It
204 has already had all the header prepping from the standard package.
205 The next step after running the hook will be to push the buffer into a
206 subprocess that mails the mail. The hook might be interested in these
207 buffers: (1) feedmail-prepped-text-buffer contains the header and body
208 of the message, ready to go; (2) feedmail-address-buffer contains the
209 space-separated, simplified list of addresses which is to be given to
210 the subprocess (the hook may change them). feedmail-error-buffer is
211 an empty buffer intended to soak up errors for display to the user.
212 If the hook allows interactive activity, the user should not send more
213 mail while in the hook since some of the internal buffers will be reused.")
214
215 ;; XEmacs change: make the default more sensible.
216 (defvar feedmail-buffer-eating-function
217 (if (and (boundp 'sendmail-program)
218 (string-match "sendmail" sendmail-program))
219 'feedmail-buffer-to-sendmail
220 'feedmail-buffer-to-binmail)
221 "*Function used to send the prepped buffer to a subprocess. The
222 function's three (mandatory) arguments are: (1) the buffer containing
223 the prepped message; (2) a buffer where errors should be directed; and
224 (3) a string containing the space-separated list of simplified
225 addresses. Two popular choices for this are 'feedmail-buffer-to-binmail
226 and 'feedmail-buffer-to-sendmail. If you use the sendmail form, you
227 probably want to set feedmail-nuke-bcc to nil. If you use the binmail
228 form, check the value of feedmail-binmail-template.")
229
230
231 (defvar feedmail-binmail-template (if mail-interactive "/bin/mail %s" "/bin/rmail %s")
232 "*Command template for the subprocess which will get rid of the
233 mail. It can result in any command understandable by /bin/sh. The
234 single '%s', if present, gets replaced by the space-separated,
235 simplified list of addressees. Used in feedmail-buffer-to-binmail to
236 form the shell command which will receive the contents of the prepped
237 buffer as stdin. If you'd like your errors to come back as mail
238 instead of immediately in a buffer, try /bin/rmail instead of
239 /bin/mail (this can be accomplished by keeping the default nil setting
240 of mail-interactive). You might also like to consult local mail
241 experts for any other interesting command line possibilities.")
242
243
244 ;; feedmail-buffer-to-binmail and feedmail-buffer-to-sendmail are the
245 ;; only things provided for values for the variable
246 ;; feedmail-buffer-eating-function. It's pretty easy to write your
247 ;; own, though.
248
249 (defun feedmail-buffer-to-binmail (prepped-mail-buffer mail-error-buffer simple-address-list)
250 "Function which actually calls /bin/mail as a subprocess and feeds the buffer to it."
251 (save-excursion
252 (set-buffer prepped-mail-buffer)
253 (apply 'call-process-region
254 (append (list (point-min) (point-max)
255 "/bin/sh" nil mail-error-buffer nil "-c"
256 (format feedmail-binmail-template simple-address-list ))))
257 ) ;; save-excursion
258 )
259
260
261 (defun feedmail-buffer-to-sendmail (prepped-mail-buffer feedmail-error-buffer simple-address-list)
262 "Function which actually calls sendmail as a subprocess and feeds the buffer to it."
263 (save-excursion
264 (set-buffer prepped-mail-buffer)
265 (apply 'call-process-region
266 (append (list (point-min) (point-max)
267 (if (boundp 'sendmail-program)
268 sendmail-program
269 "/usr/lib/sendmail")
270 nil feedmail-error-buffer nil
271 "-oi" "-t")
272 ;; Don't say "from root" if running under su.
273 (and (equal (user-real-login-name) "root")
274 (list "-f" (user-login-name)))
275 ;; These mean "report errors by mail"
276 ;; and "deliver in background".
277 (if (null mail-interactive) '("-oem" "-odb"))))
278 ))
279
280
281 ;; feedmail-send-it is the only "public" function is this file.
282 ;; All of the others are just little helpers.
283 ;;;###autoload
284 (defun feedmail-send-it ()
285 (let* ((default-case-fold-search t)
286 (feedmail-error-buffer (get-buffer-create " *Outgoing Email Errors*"))
287 (feedmail-prepped-text-buffer (get-buffer-create " *Outgoing Email Text*"))
288 (feedmail-address-buffer (get-buffer-create " *Outgoing Email Address List*"))
289 (feedmail-raw-text-buffer (current-buffer))
290 (case-fold-search nil)
291 end-of-headers-marker)
292
293 (unwind-protect (save-excursion
294 (set-buffer feedmail-prepped-text-buffer) (erase-buffer)
295
296 ;; jam contents of user-supplied mail buffer into our scratch buffer
297 (insert-buffer-substring feedmail-raw-text-buffer)
298
299 ;; require one newline at the end.
300 (goto-char (point-max))
301 (or (= (preceding-char) ?\n) (insert ?\n))
302
303 ;; Change header-delimiter to be what mailers expect (empty line).
304 (goto-char (point-min))
305 (re-search-forward (concat "^" (regexp-quote mail-header-separator) "\n"))
306 (replace-match "\n")
307 ;; why was this backward-char here?
308 ;;(backward-char 1)
309 (setq end-of-headers-marker (point-marker))
310
311 (if (and (fboundp 'expand-mail-aliases) ; nil = mail-abbrevs.el
312 mail-aliases)
313 (expand-mail-aliases (point-min) end-of-headers-marker))
314
315 ;; make it pretty
316 (if feedmail-fill-to-cc (feedmail-fill-to-cc-function end-of-headers-marker))
317 ;; ignore any blank lines in the header
318 (goto-char (point-min))
319 (while (and (re-search-forward "\n\n\n*" end-of-headers-marker t) (< (point) end-of-headers-marker))
320 (replace-match "\n"))
321
322 (let ((case-fold-search t))
323 (feedmail-deduce-address-list feedmail-prepped-text-buffer (point-min) end-of-headers-marker)
324 (save-excursion (set-buffer feedmail-address-buffer)
325 (goto-char (point-min))
326 (if (not (re-search-forward "\\S-" (point-max) t))
327 (error "Sending...abandoned, no addressees!")))
328
329 ;; Find and handle any BCC fields.
330 (if feedmail-nuke-bcc (feedmail-do-bcc end-of-headers-marker))
331
332 ;; Find and handle any FCC fields.
333 (goto-char (point-min))
334 (if (re-search-forward "^FCC:" end-of-headers-marker t)
335 (mail-do-fcc end-of-headers-marker))
336
337 (goto-char (point-min))
338 (if (re-search-forward "^FROM:" end-of-headers-marker t)
339
340 ;; If there is a FROM: and no SENDER:, put in a SENDER:
341 ;; if requested by user
342 (if (and feedmail-sender-line
343 (not (save-excursion (goto-char (point-min))
344 (re-search-forward "^SENDER:" end-of-headers-marker t))))
345 (progn (forward-line 1) (insert "Sender: " feedmail-sender-line "\n")))
346
347 ;; no FROM: ... force one?
348 (if feedmail-from-line
349 (progn (goto-char (point-min)) (insert "From: " feedmail-from-line "\n")))
350 )
351
352 ;; don't send out a blank subject line
353 (goto-char (point-min))
354 (if (re-search-forward "^Subject:[ \t]*\n" end-of-headers-marker t)
355 (replace-match ""))
356
357 ;; don't send out a blank headers of various sorts
358 (goto-char (point-min))
359 (and feedmail-nuke-empty-headers ;; hey, who's an empty-header?
360 (while (re-search-forward "^[A-Za-z0-9-]+:[ \t]*\n" end-of-headers-marker t)
361 (replace-match ""))))
362
363 ;; message ID generation
364 (if feedmail-message-id-generator
365 (progn
366 (goto-char (point-min))
367 (if (re-search-forward "^MESSAGE-ID:[ \t]*\n" end-of-headers-marker t)
368 (replace-match ""))
369 (setq feedmail-msgid-part (funcall feedmail-message-id-generator))
370 (goto-char (point-min))
371 (and feedmail-msgid-part (string-match "[^ \t]" feedmail-msgid-part)
372 (insert "Message-ID: " feedmail-msgid-part "\n"))))
373
374
375 (save-excursion (set-buffer feedmail-error-buffer) (erase-buffer))
376
377 (run-hooks 'feedmail-last-chance-hook)
378
379 (if (or (not feedmail-confirm-outgoing) (feedmail-one-last-look feedmail-prepped-text-buffer))
380 (funcall feedmail-buffer-eating-function feedmail-prepped-text-buffer feedmail-error-buffer
381 (save-excursion (set-buffer feedmail-address-buffer) (buffer-string)))
382 (error "Sending...abandoned")
383 )
384 ) ;; unwind-protect body (save-excursion)
385
386 ;; unwind-protect cleanup forms
387 (kill-buffer feedmail-prepped-text-buffer)
388 (kill-buffer feedmail-address-buffer)
389 (set-buffer feedmail-error-buffer)
390 (if (zerop (buffer-size))
391 (kill-buffer feedmail-error-buffer)
392 (progn (display-buffer feedmail-error-buffer)
393 (error "Sending...failed")))
394 (set-buffer feedmail-raw-text-buffer))
395 ) ;; let
396 )
397
398
399 (defun feedmail-do-bcc (header-end)
400 "Delete BCC: and their continuation lines from the header area.
401 There may be multiple BCC: lines, and each may have arbitrarily
402 many continuation lines."
403 (let ((case-fold-search t))
404 (save-excursion (goto-char (point-min))
405 ;; iterate over all BCC: lines
406 (while (re-search-forward "^BCC:" header-end t)
407 (delete-region (match-beginning 0) (progn (forward-line 1) (point)))
408 ;; get rid of any continuation lines
409 (while (and (looking-at "^[ \t].*\n") (< (point) header-end))
410 (replace-match ""))
411 )
412 ) ;; save-excursion
413 ) ;; let
414 )
415
416 (defun feedmail-fill-to-cc-function (header-end)
417 "Smart filling of TO: and CC: headers. The filling tries to avoid
418 splitting lines except at commas. This avoids, in particular,
419 splitting within parenthesized comments in addresses."
420 (let ((case-fold-search t)
421 (fill-prefix "\t")
422 (fill-column feedmail-fill-to-cc-fill-column)
423 this-line
424 this-line-end)
425 (save-excursion (goto-char (point-min))
426 ;; iterate over all TO:/CC: lines
427 (while (re-search-forward "^\\(TO:\\|CC:\\)" header-end t)
428 (setq this-line (match-beginning 0))
429 (forward-line 1)
430 ;; get any continuation lines
431 (while (and (looking-at "^[ \t]+") (< (point) header-end))
432 (replace-match " ")
433 (forward-line 1))
434 (setq this-line-end (point-marker))
435
436 ;; The general idea is to break only on commas. Change
437 ;; all the blanks to something unprintable; change the
438 ;; commas to blanks; fill the region; change it back.
439 (subst-char-in-region this-line this-line-end ? 2 t) ;; blank --> C-b
440 (subst-char-in-region this-line this-line-end ?, ? t) ;; comma --> blank
441 (fill-region-as-paragraph this-line this-line-end)
442
443 (subst-char-in-region this-line this-line-end ? ?, t) ;; comma <-- blank
444 (subst-char-in-region this-line this-line-end 2 ? t) ;; blank <-- C-b
445
446 ;; look out for missing commas before continuation lines
447 (save-excursion
448 (goto-char this-line)
449 (while (re-search-forward "\\([^,]\\)\n\t[ ]*" this-line-end t)
450 (replace-match "\\1,\n\t")))
451 )
452 ) ;; while
453 ) ;; save-excursion
454 )
455
456
457 (defun feedmail-deduce-address-list (feedmail-text-buffer header-start header-end)
458 "Get address list suitable for command line use on simple /bin/mail."
459 (require 'mail-utils) ;; pick up mail-strip-quoted-names
460 (let
461 ((case-fold-search t)
462 (simple-address-list "")
463 this-line
464 this-line-end)
465 (unwind-protect
466 (save-excursion
467 (set-buffer feedmail-address-buffer) (erase-buffer)
468 (insert-buffer-substring feedmail-text-buffer header-start header-end)
469 (goto-char (point-min))
470 (while (re-search-forward "^\\(TO:\\|CC:\\|BCC:\\)" header-end t)
471 (replace-match "")
472 (setq this-line (match-beginning 0))
473 (forward-line 1)
474 ;; get any continuation lines
475 (while (and (looking-at "^[ \t]+") (< (point) header-end))
476 (forward-line 1))
477 (setq this-line-end (point-marker))
478 (setq simple-address-list
479 (concat simple-address-list " "
480 (mail-strip-quoted-names (buffer-substring this-line this-line-end))))
481 )
482 (erase-buffer)
483 (insert-string simple-address-list)
484 (subst-char-in-region (point-min) (point-max) 10 ? t) ;; newline --> blank
485 (subst-char-in-region (point-min) (point-max) ?, ? t) ;; comma --> blank
486 (subst-char-in-region (point-min) (point-max) 9 ? t) ;; tab --> blank
487
488 (goto-char (point-min))
489 ;; tidyness in case hook is not robust when it looks at this
490 (while (re-search-forward "[ \t]+" header-end t) (replace-match " "))
491
492 )
493 )
494 )
495 )
496
497
498 (defun feedmail-one-last-look (feedmail-prepped-text-buffer)
499 "Offer the user one last chance to give it up."
500 (save-excursion (save-window-excursion
501 (switch-to-buffer feedmail-prepped-text-buffer)
502 (y-or-n-p "Send this email? "))))
503
504
505 (provide 'feedmail)