diff 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
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lisp/packages/feedmail.el	Mon Aug 13 08:45:50 2007 +0200
@@ -0,0 +1,505 @@
+;;; 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")
+;;;
+
+
+(defvar feedmail-confirm-outgoing nil
+  "*If non-nil, gives a y-or-n confirmation prompt after prepping,
+before sending mail.")
+
+
+(defvar 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).")
+
+
+(defvar 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.")
+
+
+(defvar feedmail-fill-to-cc-fill-column default-fill-column
+  "*Fill column used when wrapping mail TO: and CC: lines.")
+
+
+(defvar 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.")
+
+;;; 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.
+
+(defvar 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.")
+
+
+;; user-full-name suggested by kpc@ptolemy.arc.nasa.gov (=Kimball Collins)
+(defvar 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.")
+
+
+;;; 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)
+;;;   		 ))
+;;;   
+(defvar 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.")
+
+
+(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"))))
+
+
+(defvar 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.")
+
+;; XEmacs change: make the default more sensible.
+(defvar 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.")
+
+
+(defvar 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.")
+
+
+;; 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)