File: feedmail.el

package info (click to toggle)
xemacs20 20.4-13
  • links: PTS
  • area: main
  • in suites: slink
  • size: 67,324 kB
  • ctags: 57,643
  • sloc: lisp: 586,197; ansic: 184,662; sh: 4,296; asm: 3,179; makefile: 2,021; perl: 1,059; csh: 96; sed: 22
file content (531 lines) | stat: -rw-r--r-- 20,709 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
;;; feedmail.el --- outbound mail handling

;; Keywords: mail

;;; Synched up with: Not in FSF.

;;; From: William.J.Carpenter@hos1cad.att.com (Bill C)
;;; Subject: feedmail.el, patchlevel 2 [repost]
;;; Date: 8 Jun 91 22:23:00 GMT
;;; Organization: AT&T Bell Laboratories
;;;
;;; 5-may-92  jwz	Conditionalized calling expand-mail-aliases, since that
;;;			function doesn't exist in Lucid GNU Emacs or when using
;;;			mail-abbrevs.el.
;;; 
;;; Here's the latest version of feedmail.el, a replacement for parts of
;;; GNUemacs' sendmail.el (specifically, it's what handles your outgoing
;;; mail after you type C-c C-c in mail mode).   (Sorry if you're seeing
;;; this a second time.  Looks like my earlier attempt to post it didn't
;;; get off the local machine.)
;;; 
;;; This version contains the following new things:
;;; 
;;;    * fix for handling default-case-fold-search
;;;    * involve user-full-name in default from line
;;;    * fix for my improper use of mail-strip-quoted-names when
;;;      addresses contain a mix of "<>" and "()" styles
;;;    * new feature allowing optional generation of Message-ID

;;; feedmail.el
;;; LCD record:
;;; feedmail|Bill Carpenter|william.j.carpenter@att.com|Outbound mail handling|91-05-24|2|feedmail.el
;;;
;;; Written by Bill Carpenter <william.j.carpenter@att.com>
;;; original,      31 March 1991
;;; patchlevel 1,   5 April 1991
;;; patchlevel 2,  24 May   1991
;;;
;;; As far as I'm concerned, anyone can do anything they want with
;;; this specific piece of code.  No warranty or promise of support is
;;; offered.
;;;
;;; This stuff does in elisp the stuff that used to be done
;;; by the separate program "fakemail" for processing outbound email.
;;; In other words, it takes over after you hit "C-c C-c" in mail mode.
;;; By appropriate setting of options, you can still use "fakemail",
;;; or you can even revert to sendmail (which is not too popular
;;; locally).  See the variables at the top of the elisp for how to
;;; achieve these effects:
;;;
;;;    --- you can get one last look at the prepped outbound message and
;;;        be prompted for confirmation
;;;
;;;    --- removes BCC: headers after getting address info
;;;
;;;    --- does smart filling of TO: and CC: headers
;;;
;;;    --- processes FCC: lines and removes them
;;;
;;;    --- empty headers are removed
;;;
;;;    --- can force FROM: or SENDER: line
;;;
;;;    --- can generate a Message-ID line
;;;
;;;    --- strips comments from address info (both "()" and "<>" are
;;;        handled via a call to mail-strip-quoted-names); the
;;;        comments are stripped in the simplified address list given
;;;        to a subprocess, not in the headers in the mail itself
;;;        (they are left unchanged, modulo smart filling)
;;;
;;;    --- error info is pumped into a normal buffer instead of the
;;;        minibuffer
;;;
;;;    --- just before the optional prompt for confirmation, lets you
;;;        run a hook on the prepped message and simplified address
;;;        list
;;;
;;;    --- you can specify something other than /bin/mail for the
;;;        subprocess
;;;
;;; After a few options below, you will find the function
;;; feedmail-send-it.  Everything after that function is just local
;;; stuff for this file.  There are two ways you can use the stuff in
;;; this file:
;;;
;;; (1)  Put the contents of this file into sendmail.el and change the
;;; name of feedmail-send-it to sendmail-send-it, replacing that
;;; function in sendmail.el.
;;;
;;;                              or
;;;
;;; (2)  Save this file as feedmail.el somewhere on your elisp
;;; loadpath; byte-compile it.  Put the following lines somewhere in
;;; your ~/.emacs stuff:
;;;
;;;        (setq send-mail-function 'feedmail-send-it)
;;;        (autoload 'feedmail-send-it "feedmail")
;;;

(defgroup feedmail nil
  "Outbound mail handling."
  :group 'mail)


(defcustom feedmail-confirm-outgoing nil
  "*If non-nil, gives a y-or-n confirmation prompt after prepping,
before sending mail."
  :type 'boolean
  :group 'feedmail)


(defcustom feedmail-nuke-bcc t
  "*Non-nil means get rid of the BCC: lines from the message header
text before sending the mail.  In any case, the BCC: lines do
participate in the composed address list.  You probably want to keep
them if you're using sendmail (see feedmail-buffer-eating-function)."
  :type 'boolean
  :group 'feedmail)


(defcustom feedmail-fill-to-cc t
  "*Non-nil means do smart filling (line-wrapping) of TO: and CC: header
lines.  If nil, the lines are left as-is.  The filling is done after
mail address alias expansion."
  :type 'boolean
  :group 'feedmail)


(defcustom feedmail-fill-to-cc-fill-column default-fill-column
  "*Fill column used when wrapping mail TO: and CC: lines."
  :type 'integer
  :group 'feedmail)


(defcustom feedmail-nuke-empty-headers t
  "*If non-nil, headers with no contents are removed from the outgoing
email.  A completely empty SUBJECT: header is always removed,
regardless of the setting of this variable.  The only time you would
want them left in would be if you used some headers whose presence
indicated something rather than their contents."
  :type 'boolean
  :group 'feedmail)

;;; wjc sez:  I think the use of the SENDER: line is pretty pointless,
;;; but I left it in to be compatible with sendmail.el and because
;;; maybe some distant mail system needs it.  Really, though, if you
;;; want a sender line in your mail, just put one in there and don't
;;; wait for feedmail to do it for you.

(defcustom feedmail-sender-line nil
  "*If nil, no SENDER: header is forced.  If non-nil and the email
already has a FROM: header, a SENDER: header is forced with this as
its contents.  You can probably leave this nil, but if you feel like
using it, a good value would be a fully-qualified domain name form of
your address.  For example, william.j.carpenter@att.com.  Don't
include a trailing newline or the keyword SENDER:.  They're
automatically provided."
  :type 'boolean
  :group 'feedmail)


;; user-full-name suggested by kpc@ptolemy.arc.nasa.gov (=Kimball Collins)
(defcustom feedmail-from-line
  (concat (user-login-name) "@" (system-name) " (" (user-full-name) ")")
  "*If non-nil and the email has no FROM: header, one will be forced
with this as its contents. A good value would be a fully-qualified
domain name form of your address.  For example, william.j.carpenter@att.com.
(The default value of this variable is probably not very good, since
it doesn't have a domain part.)  Don't include a trailing newline or
the keyword FROM:.  They're automatically provided."
  :type '(choice (const nil) string)
  :group 'feedmail)


;;; Here's how I use the GNUS Message-ID generator for mail but not
;;; for news postings:
;;;
;;;   (setq feedmail-message-id-generator 'wjc:gnusish-message-id)
;;;   (setq gnus-your-domain "hos1cad.ATT.COM")
;;;   
;;;   (defun wjc:gnusish-message-id ()
;;;     (require 'gnuspost)
;;;     (if (fboundp 'wjc:gnus-inews-message-id)
;;;   	  (wjc:gnus-inews-message-id)
;;;   	(gnus-inews-message-id)))
;;;   
;;;   (setq news-inews-hook
;;;   	  '(lambda () 
;;;   		 (defun gnus-inews-date () nil)
;;;   		 (fset 'wjc:gnus-inews-message-id (symbol-function 'gnus-inews-message-id))
;;;   		 (defun gnus-inews-message-id () nil)
;;;   		 ))
;;;   
(defcustom feedmail-message-id-generator nil
  "*If non-nil, should be a function (called with no arguments) which
will generate a unique message ID which will be inserted on a
Message-ID: header.  The message ID should be the return value of the
function.  Don't include trailing newline, leading space, or the
keyword MESSAGE-ID.  They're automatically provided.  Do include
surrounding <> brackets.  For an example of a message ID generating
function, you could look at the GNUS function gnus-inews-message-id.
When called, the current buffer is the prepped outgoing mail buffer
(the function may inspect it, but shouldn't modify it).  If the returned
value doesn't contain any non-whitespace characters, no message ID
header is generated, so you could generate them conditionally,
based on the contents of the mail."
  :type 'boolean
  :group 'feedmail)


(defun feedmail-confirm-addresses-hook-example ()
  "An example of a last chance hook that shows the simple addresses
and gets a confirmation.  Use as (setq feedmail-last-chance-hook
'feedmail-confirm-addresses-hook-example)."
  (save-window-excursion 
	(display-buffer feedmail-address-buffer)
	(if (not (y-or-n-p "How do you like them apples? "))
		(error "Sending...gave up in last chance hook"))))


(defcustom feedmail-last-chance-hook nil
  "*User's last opportunity to modify the message on its way out.  It
has already had all the header prepping from the standard package.
The next step after running the hook will be to push the buffer into a
subprocess that mails the mail.  The hook might be interested in these
buffers:  (1) feedmail-prepped-text-buffer contains the header and body
of the message, ready to go;  (2) feedmail-address-buffer contains the
space-separated, simplified list of addresses which is to be given to
the subprocess (the hook may change them).  feedmail-error-buffer is
an empty buffer intended to soak up errors for display to the user.
If the hook allows interactive activity, the user should not send more
mail while in the hook since some of the internal buffers will be reused."
  :type 'hook
  :group 'feedmail)

;; XEmacs change: make the default more sensible.
(defcustom feedmail-buffer-eating-function
  (if (and (boundp 'sendmail-program)
	   (string-match "sendmail" sendmail-program))
      'feedmail-buffer-to-sendmail
    'feedmail-buffer-to-binmail)
  "*Function used to send the prepped buffer to a subprocess.  The
function's three (mandatory) arguments are: (1) the buffer containing
the prepped message; (2) a buffer where errors should be directed; and
(3) a string containing the space-separated list of simplified
addresses.  Two popular choices for this are 'feedmail-buffer-to-binmail
and 'feedmail-buffer-to-sendmail.  If you use the sendmail form, you
probably want to set feedmail-nuke-bcc to nil.  If you use the binmail
form, check the value of feedmail-binmail-template."
  :type 'function
  :group 'feedmail)


(defcustom feedmail-binmail-template (if mail-interactive "/bin/mail %s" "/bin/rmail %s")
  "*Command template for the subprocess which will get rid of the
mail.  It can result in any command understandable by /bin/sh.  The
single '%s', if present, gets replaced by the space-separated,
simplified list of addressees.  Used in feedmail-buffer-to-binmail to
form the shell command which will receive the contents of the prepped
buffer as stdin.  If you'd like your errors to come back as mail
instead of immediately in a buffer, try /bin/rmail instead of
/bin/mail (this can be accomplished by keeping the default nil setting
of mail-interactive).  You might also like to consult local mail
experts for any other interesting command line possibilities."
  :type 'string
  :group 'feedmail)


;; feedmail-buffer-to-binmail and feedmail-buffer-to-sendmail are the
;; only things provided for values for the variable
;; feedmail-buffer-eating-function.  It's pretty easy to write your
;; own, though.

(defun feedmail-buffer-to-binmail (prepped-mail-buffer mail-error-buffer simple-address-list)
  "Function which actually calls /bin/mail as a subprocess and feeds the buffer to it."
  (save-excursion
	(set-buffer prepped-mail-buffer)
	(apply 'call-process-region
		   (append (list (point-min) (point-max)
						 "/bin/sh" nil mail-error-buffer nil "-c"
						 (format feedmail-binmail-template simple-address-list ))))
	) ;; save-excursion
  )


(defun feedmail-buffer-to-sendmail (prepped-mail-buffer feedmail-error-buffer simple-address-list)
  "Function which actually calls sendmail as a subprocess and feeds the buffer to it."
  (save-excursion
	(set-buffer prepped-mail-buffer)
	(apply 'call-process-region
		   (append (list (point-min) (point-max)
					   (if (boundp 'sendmail-program)
						   sendmail-program
						 "/usr/lib/sendmail")
					   nil feedmail-error-buffer nil
					   "-oi" "-t")
				 ;; Don't say "from root" if running under su.
				 (and (equal (user-real-login-name) "root")
					  (list "-f" (user-login-name)))
				 ;; These mean "report errors by mail"
				 ;; and "deliver in background".
				 (if (null mail-interactive) '("-oem" "-odb"))))
))


;; feedmail-send-it is the only "public" function is this file.
;; All of the others are just little helpers.
;;;###autoload
(defun feedmail-send-it ()
  (let* ((default-case-fold-search t)
		 (feedmail-error-buffer (get-buffer-create " *Outgoing Email Errors*"))
		 (feedmail-prepped-text-buffer (get-buffer-create " *Outgoing Email Text*"))
		 (feedmail-address-buffer (get-buffer-create " *Outgoing Email Address List*"))
		 (feedmail-raw-text-buffer (current-buffer))
		 (case-fold-search nil)
		 end-of-headers-marker)

    (unwind-protect (save-excursion
		(set-buffer feedmail-prepped-text-buffer) (erase-buffer)

		;; jam contents of user-supplied mail buffer into our scratch buffer
		(insert-buffer-substring feedmail-raw-text-buffer)

		;; require one newline at the end.
		(goto-char (point-max))
		(or (= (preceding-char) ?\n) (insert ?\n))

		;; Change header-delimiter to be what mailers expect (empty line).
		(goto-char (point-min))
		(re-search-forward (concat "^" (regexp-quote mail-header-separator) "\n"))
		(replace-match "\n")
		;; why was this backward-char here?
		;;(backward-char 1)
		(setq end-of-headers-marker (point-marker))

		(if (and (fboundp 'expand-mail-aliases) ; nil = mail-abbrevs.el
			 mail-aliases)
		    (expand-mail-aliases (point-min) end-of-headers-marker))

		;; make it pretty
		(if feedmail-fill-to-cc (feedmail-fill-to-cc-function end-of-headers-marker))
		;; ignore any blank lines in the header
		(goto-char (point-min))
		(while (and (re-search-forward "\n\n\n*" end-of-headers-marker t) (< (point) end-of-headers-marker))
		  (replace-match "\n"))
	  
		(let ((case-fold-search t))
		  (feedmail-deduce-address-list feedmail-prepped-text-buffer (point-min) end-of-headers-marker)
		  (save-excursion (set-buffer feedmail-address-buffer)
						  (goto-char (point-min))
						  (if (not (re-search-forward "\\S-" (point-max) t))
							  (error "Sending...abandoned, no addressees!")))

		  ;; Find and handle any BCC fields.
		  (if feedmail-nuke-bcc (feedmail-do-bcc end-of-headers-marker))

		  ;; Find and handle any FCC fields.
		  (goto-char (point-min))
		  (if (re-search-forward "^FCC:" end-of-headers-marker t)
			  (mail-do-fcc end-of-headers-marker))

		  (goto-char (point-min))
		  (if (re-search-forward "^FROM:" end-of-headers-marker t)
			  
			  ;; If there is a FROM: and no SENDER:, put in a SENDER:
			  ;; if requested by user
			  (if (and feedmail-sender-line
					   (not (save-excursion (goto-char (point-min))
						   (re-search-forward "^SENDER:" end-of-headers-marker t))))
				  (progn (forward-line 1) (insert "Sender: " feedmail-sender-line "\n")))

			;; no FROM: ... force one?
			(if feedmail-from-line
				(progn (goto-char (point-min)) (insert "From: " feedmail-from-line "\n")))
			)

		  ;; don't send out a blank subject line
		  (goto-char (point-min))
		  (if (re-search-forward "^Subject:[ \t]*\n" end-of-headers-marker t)
			  (replace-match ""))

		  ;; don't send out a blank headers of various sorts
		  (goto-char (point-min))
		  (and feedmail-nuke-empty-headers  ;; hey, who's an empty-header? 
			   (while (re-search-forward "^[A-Za-z0-9-]+:[ \t]*\n" end-of-headers-marker t)
				 (replace-match ""))))

		;; message ID generation
		(if feedmail-message-id-generator
			(progn
			  (goto-char (point-min))
			  (if (re-search-forward "^MESSAGE-ID:[ \t]*\n" end-of-headers-marker t)
				  (replace-match ""))
			  (setq feedmail-msgid-part (funcall feedmail-message-id-generator))
			  (goto-char (point-min))
			  (and feedmail-msgid-part (string-match "[^ \t]" feedmail-msgid-part)
				  (insert "Message-ID: " feedmail-msgid-part "\n"))))


		(save-excursion (set-buffer feedmail-error-buffer) (erase-buffer))

		(run-hooks 'feedmail-last-chance-hook)

		(if (or (not feedmail-confirm-outgoing) (feedmail-one-last-look feedmail-prepped-text-buffer))
			(funcall feedmail-buffer-eating-function feedmail-prepped-text-buffer feedmail-error-buffer
					 (save-excursion (set-buffer feedmail-address-buffer) (buffer-string)))
		  (error "Sending...abandoned")
		  )
		)  ;; unwind-protect body (save-excursion)

	  ;; unwind-protect cleanup forms
	  (kill-buffer feedmail-prepped-text-buffer)
	  (kill-buffer feedmail-address-buffer)
	  (set-buffer feedmail-error-buffer)
	  (if (zerop (buffer-size))
		  (kill-buffer feedmail-error-buffer)
		(progn (display-buffer feedmail-error-buffer)
			   (error "Sending...failed")))
	  (set-buffer feedmail-raw-text-buffer))
	) ;; let
  )


(defun feedmail-do-bcc (header-end)
  "Delete BCC: and their continuation lines from the header area.
There may be multiple BCC: lines, and each may have arbitrarily
many continuation lines."
  (let ((case-fold-search t))
	(save-excursion (goto-char (point-min))
	  ;; iterate over all BCC: lines
	  (while (re-search-forward "^BCC:" header-end t)
		(delete-region (match-beginning 0) (progn (forward-line 1) (point)))
		;; get rid of any continuation lines
		(while (and (looking-at "^[ \t].*\n") (< (point) header-end))
		  (replace-match ""))
		)
	  ) ;; save-excursion
	) ;; let
  )

(defun feedmail-fill-to-cc-function (header-end)
  "Smart filling of TO: and CC: headers.  The filling tries to avoid
splitting lines except at commas.  This avoids, in particular,
splitting within parenthesized comments in addresses."
  (let ((case-fold-search t)
		(fill-prefix "\t")
		(fill-column feedmail-fill-to-cc-fill-column)
		this-line
		this-line-end)
	(save-excursion (goto-char (point-min))
	  ;; iterate over all TO:/CC: lines
	  (while (re-search-forward "^\\(TO:\\|CC:\\)" header-end t)
		(setq this-line (match-beginning 0))
		(forward-line 1)
		;; get any continuation lines
		(while (and (looking-at "^[ \t]+") (< (point) header-end))
		  (replace-match " ")
		  (forward-line 1))
		(setq this-line-end (point-marker))

		;; The general idea is to break only on commas.  Change
		;; all the blanks to something unprintable; change the
		;; commas to blanks; fill the region; change it back.
		(subst-char-in-region this-line this-line-end ?   2 t) ;; blank --> C-b
		(subst-char-in-region this-line this-line-end ?, ?  t) ;; comma --> blank
		(fill-region-as-paragraph this-line this-line-end)

		(subst-char-in-region this-line this-line-end ?  ?, t) ;; comma <-- blank
		(subst-char-in-region this-line this-line-end  2 ?  t) ;; blank <-- C-b

		;; look out for missing commas before continuation lines
		(save-excursion
		  (goto-char this-line)
		  (while (re-search-forward "\\([^,]\\)\n\t[ ]*" this-line-end t)
			(replace-match "\\1,\n\t")))
		)
	  ) ;; while
	) ;; save-excursion
  )


(defun feedmail-deduce-address-list (feedmail-text-buffer header-start header-end)
  "Get address list suitable for command line use on simple /bin/mail."
  (require 'mail-utils)  ;; pick up mail-strip-quoted-names
  (let
	  ((case-fold-search t)
	   (simple-address-list "")
	   this-line
	   this-line-end)
	(unwind-protect
		(save-excursion
		  (set-buffer feedmail-address-buffer) (erase-buffer)
		  (insert-buffer-substring feedmail-text-buffer header-start header-end)
		  (goto-char (point-min))
		  (while (re-search-forward "^\\(TO:\\|CC:\\|BCC:\\)" header-end t)
			(replace-match "")
			(setq this-line (match-beginning 0))
			(forward-line 1)
			;; get any continuation lines
			(while (and (looking-at "^[ \t]+") (< (point) header-end))
			  (forward-line 1))
			(setq this-line-end (point-marker))
			(setq simple-address-list
				  (concat simple-address-list " "
						  (mail-strip-quoted-names (buffer-substring this-line this-line-end))))
			)
		  (erase-buffer)
		  (insert-string simple-address-list)
		  (subst-char-in-region (point-min) (point-max) 10 ?  t)  ;; newline --> blank
		  (subst-char-in-region (point-min) (point-max) ?, ?  t)  ;; comma   --> blank
		  (subst-char-in-region (point-min) (point-max)  9 ?  t)  ;; tab     --> blank

		  (goto-char (point-min))
		  ;; tidyness in case hook is not robust when it looks at this
		  (while (re-search-forward "[ \t]+" header-end t) (replace-match " "))

		  )
	  )
	)
  )


(defun feedmail-one-last-look (feedmail-prepped-text-buffer)
  "Offer the user one last chance to give it up."
  (save-excursion (save-window-excursion
	(switch-to-buffer feedmail-prepped-text-buffer)
	(y-or-n-p "Send this email? "))))


(provide 'feedmail)