2c8abc9a2dbc563e6fb14214b5cb4a2bb54321ce
[gnus] / lisp / message.el
1 ;;; message.el --- composing mail and news messages
2 ;; Copyright (C) 1996 Free Software Foundation, Inc.
3
4 ;; Author: Lars Magne Ingebrigtsen <larsi@ifi.uio.no>
5 ;; Keywords: mail, news
6
7 ;; This file is part of GNU Emacs.
8
9 ;; GNU Emacs is free software; you can redistribute it and/or modify
10 ;; it under the terms of the GNU General Public License as published by
11 ;; the Free Software Foundation; either version 2, or (at your option)
12 ;; any later version.
13
14 ;; GNU Emacs is distributed in the hope that it will be useful,
15 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
16 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 ;; GNU General Public License for more details.
18
19 ;; You should have received a copy of the GNU General Public License
20 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
21 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
22 ;; Boston, MA 02111-1307, USA.
23
24 ;;; Commentary:
25
26 ;; This mode provides mail-sending facilities from within Emacs.  It
27 ;; consists mainly of large chunks of code from the sendmail.el,
28 ;; gnus-msg.el and rnewspost.el files.
29
30 ;;; Code:
31
32 (eval-when-compile 
33   (require 'cl))
34 (require 'mailheader)
35 (require 'rmail)
36 (require 'nnheader)
37 (require 'timezone)
38 (require 'easymenu)
39 (if (string-match "XEmacs\\|Lucid" emacs-version)
40     (require 'mail-abbrevs)
41   (require 'mailabbrev))
42
43 (defvar message-directory "~/Mail/"
44   "*Directory from which all other mail file variables are derived.")
45
46 (defvar message-max-buffers 10
47   "*How many buffers to keep before starting to kill them off.")
48
49 (defvar message-send-rename-function nil
50   "Function called to rename the buffer after sending it.")
51
52 ;;;###autoload
53 (defvar message-fcc-handler-function 'rmail-output
54   "*A function called to save outgoing articles.
55 This function will be called with the name of the file to store the
56 article in. The default function is `rmail-output' which saves in Unix
57 mailbox format.")
58
59 ;;;###autoload
60 (defvar message-courtesy-message
61   "The following message is a courtesy copy of an article\nthat has been posted as well.\n\n"
62   "*This is inserted at the start of a mailed copy of a posted message.
63 If this variable is nil, no such courtesy message will be added.")
64
65 ;;;###autoload
66 (defvar message-ignored-bounced-headers "^\\(Received\\|Return-Path\\):"
67   "*Regexp that matches headers to be removed in resent bounced mail.")
68
69 ;;;###autoload
70 (defvar message-from-style 'default
71   "*Specifies how \"From\" headers look.
72
73 If `nil', they contain just the return address like:
74         king@grassland.com
75 If `parens', they look like:
76         king@grassland.com (Elvis Parsley)
77 If `angles', they look like:
78         Elvis Parsley <king@grassland.com>
79
80 Otherwise, most addresses look like `angles', but they look like
81 `parens' if `angles' would need quoting and `parens' would not.")
82
83 ;;;###autoload
84 (defvar message-syntax-checks nil
85   "Controls what syntax checks should not be performed on outgoing posts.
86 To disable checking of long signatures, for instance, add
87  `(signature . disabled)' to this list.
88
89 Don't touch this variable unless you really know what you're doing.
90
91 Checks include subject-cmsg multiple-headers sendsys message-id from
92 long-lines control-chars size new-text redirected-followup signature
93 approved sender empty empty-headers message-id from subject.")
94
95 ;;;###autoload
96 (defvar message-required-news-headers
97   '(From Newsgroups Subject Date Message-ID 
98          (optional . Organization) Lines 
99          (optional . X-Newsreader))
100   "*Headers to be generated or prompted for when posting an article.
101 RFC977 and RFC1036 require From, Date, Newsgroups, Subject,
102 Message-ID.  Organization, Lines, In-Reply-To, Expires, and
103 X-Newsreader are optional.  If don't you want message to insert some
104 header, remove it from this list.")
105
106 ;;;###autoload
107 (defvar message-required-mail-headers 
108   '(From Subject Date (optional . In-Reply-To) Message-ID Lines
109          (optional . X-Mailer))
110   "*Headers to be generated or prompted for when mailing a message.
111 RFC822 required that From, Date, To, Subject and Message-ID be
112 included.  Organization, Lines and X-Mailer are optional.")
113
114 ;;;###autoload
115 (defvar message-deletable-headers '(Message-ID Date Lines)
116   "*Headers to be deleted if they already exist and were generated by message previously.")
117
118 ;;;###autoload
119 (defvar message-ignored-news-headers 
120   "^NNTP-Posting-Host:\\|^Xref:\\|^Bcc:\\|^Gcc:\\|^Fcc:"
121   "*Regexp of headers to be removed unconditionally before posting.")
122
123 ;;;###autoload
124 (defvar message-ignored-mail-headers "^Gcc:\\|^Fcc:"
125   "*Regexp of headers to be removed unconditionally before mailing.")
126
127 ;;;###autoload
128 (defvar message-ignored-supersedes-headers "^Path:\\|^Date\\|^NNTP-Posting-Host:\\|^Xref:\\|^Lines:\\|^Received:\\|^X-From-Line:\\|Return-Path:\\|^Supersedes:"
129   "*Header lines matching this regexp will be deleted before posting.
130 It's best to delete old Path and Date headers before posting to avoid
131 any confusion.")
132
133 ;;;###autoload
134 (defvar message-signature-separator "^-- *$"
135   "Regexp matching the signature separator.")
136
137 ;;;###autoload
138 (defvar message-interactive nil 
139   "Non-nil means when sending a message wait for and display errors.
140 nil means let mailer mail back a message to report errors.")
141
142 ;;;###autoload
143 (defvar message-generate-new-buffers t
144   "*Non-nil means that a new message buffer will be created whenever `mail-setup' is called.
145 If this is a function, call that function with three parameters:  The type,
146 the to address and the group name.  (Any of these may be nil.)  The function
147 should return the new buffer name.")
148
149 ;;;###autoload
150 (defvar message-kill-buffer-on-exit nil
151   "*Non-nil means that the message buffer will be killed after sending a message.")
152
153 (defvar gnus-local-organization)
154 (defvar message-user-organization 
155   (or (and (boundp 'gnus-local-organization)
156            gnus-local-organization)
157       (getenv "ORGANIZATION")
158       t)
159   "*String to be used as an Organization header.
160 If t, use `message-user-organization-file'.")
161
162 ;;;###autoload
163 (defvar message-user-organization-file "/usr/lib/news/organization"
164   "*Local news organization file.")
165
166 (defvar message-autosave-directory "~/"
167   ; (concat (file-name-as-directory message-directory) "drafts/")
168   "*Directory where message autosaves buffers.
169 If nil, message won't autosave.")
170
171 (defvar message-forward-start-separator 
172   "------- Start of forwarded message -------\n"
173   "*Delimiter inserted before forwarded messages.")
174
175 (defvar message-forward-end-separator
176   "------- End of forwarded message -------\n"
177   "*Delimiter inserted after forwarded messages.")
178
179 ;;;###autoload
180 (defvar message-signature-before-forwarded-message t
181   "*If non-nil, put the signature before any included forwarded message.")
182
183 ;;;###autoload
184 (defvar message-included-forward-headers 
185   "^From:\\|^Newsgroups:\\|^Subject:\\|^Date:\\|^Followup-To:\\|^Reply-To:\\|^Organization:\\|^Summary:\\|^Keywords:\\|^To:\\|^Cc:\\|^Posted-To:\\|^Mail-Copies-To:\\|^Apparently-To:\\|^Gnus-Warning:\\|^Resent-\\|^Message-ID:\\|^References:"
186   "*Regexp matching headers to be included in forwarded messages.")
187
188 ;;;###autoload
189 (defvar message-ignored-resent-headers "^Return-receipt"
190   "*All headers that match this regexp will be deleted when resending a message.")
191
192 ;;;###autoload
193 (defvar message-ignored-cited-headers "."
194   "Delete these headers from the messages you yank.")
195
196 (defvar message-cancel-message "I am canceling my own article."
197   "Message to be inserted in the cancel message.")
198
199 ;; Useful to set in site-init.el
200 ;;;###autoload
201 (defvar message-send-mail-function 'message-send-mail-with-sendmail
202   "Function to call to send the current buffer as mail.
203 The headers should be delimited by a line whose contents match the
204 variable `mail-header-separator'.
205
206 Legal values include `message-send-mail-with-mh' and
207 `message-send-mail-with-sendmail', which is the default.")
208
209 ;;;###autoload
210 (defvar message-send-news-function 'message-send-news
211   "Function to call to send the current buffer as news.
212 The headers should be delimited by a line whose contents match the
213 variable `mail-header-separator'.")
214
215 ;;;###autoload
216 (defvar message-reply-to-function nil
217   "Function that should return a list of headers.
218 This function should pick out addresses from the To, Cc, and From headers
219 and respond with new To and Cc headers.")
220
221 ;;;###autoload
222 (defvar message-wide-reply-to-function nil
223   "Function that should return a list of headers.
224 This function should pick out addresses from the To, Cc, and From headers
225 and respond with new To and Cc headers.")
226
227 ;;;###autoload
228 (defvar message-followup-to-function nil
229   "Function that should return a list of headers.
230 This function should pick out addresses from the To, Cc, and From headers
231 and respond with new To and Cc headers.")
232
233 ;;;###autoload
234 (defvar message-use-followup-to 'ask
235   "*Specifies what to do with Followup-To header.
236 If nil, ignore the header. If it is t, use its value, but query before
237 using the \"poster\" value.  If it is the symbol `ask', query the user
238 whether to ignore the \"poster\" value.  If it is the symbol `use',
239 always use the value.")
240
241 (defvar gnus-post-method)
242 (defvar gnus-select-method)
243 ;;;###autoload
244 (defvar message-post-method 
245   (cond ((and (boundp 'gnus-post-method)
246               gnus-post-method)
247          gnus-post-method)
248         ((boundp 'gnus-select-method)
249          gnus-select-method)
250         (t '(nnspool "")))
251   "Method used to post news.")
252
253 ;;;###autoload
254 (defvar message-generate-headers-first nil
255   "*If non-nil, generate all possible headers before composing.")
256
257 (defvar message-setup-hook nil
258   "Normal hook, run each time a new outgoing message is initialized.
259 The function `message-setup' runs this hook.")
260
261 (defvar message-signature-setup-hook nil
262   "Normal hook, run each time a new outgoing message is initialized.
263 It is run after the headers have been inserted and before 
264 the signature is inserted.")
265
266 (defvar message-mode-hook nil
267   "Hook run in message mode buffers.")
268
269 (defvar message-header-hook nil
270   "Hook run in a message mode buffer narrowed to the headers.")
271
272 (defvar message-header-setup-hook nil
273   "Hook called narrowed to the headers when setting up a message buffer.")
274
275 ;;;###autoload
276 (defvar message-citation-line-function 'message-insert-citation-line
277   "*Function called to insert the \"Whomever writes:\" line.")
278
279 ;;;###autoload
280 (defvar message-yank-prefix "> "
281   "*Prefix inserted on the lines of yanked messages.
282 nil means use indentation.")
283
284 (defvar message-indentation-spaces 3
285   "*Number of spaces to insert at the beginning of each cited line.
286 Used by `message-yank-original' via `message-yank-cite'.")
287
288 ;;;###autoload
289 (defvar message-cite-function 'message-cite-original
290   "*Function for citing an original message.")
291
292 ;;;###autoload
293 (defvar message-indent-citation-function 'message-indent-citation
294   "*Function for modifying a citation just inserted in the mail buffer.
295 This can also be a list of functions.  Each function can find the
296 citation between (point) and (mark t).  And each function should leave
297 point and mark around the citation text as modified.")
298
299 (defvar message-abbrevs-loaded nil)
300
301 ;;;###autoload
302 (defvar message-signature t
303   "*String to be inserted at the end of the message buffer.
304 If t, the `message-signature-file' file will be inserted instead.
305 If a function, the result from the function will be used instead.
306 If a form, the result from the form will be used instead.")
307
308 ;;;###autoload
309 (defvar message-signature-file "~/.signature"
310   "*File containing the text inserted at end of message. buffer.")
311
312 (defvar message-distribution-function nil
313   "*Function called to return a Distribution header.")
314
315 (defvar message-expires 14
316   "*Number of days before your article expires.")
317
318 (defvar message-user-path nil
319   "If nil, use the NNTP server name in the Path header.
320 If stringp, use this; if non-nil, use no host name (user name only).")
321
322 (defvar message-reply-buffer nil)
323 (defvar message-reply-headers nil)
324 (defvar message-newsreader nil)
325 (defvar message-mailer nil)
326 (defvar message-sent-message-via nil)
327 (defvar message-checksum nil)
328 (defvar message-send-actions nil
329   "A list of actions to be performed upon successful sending of a message.")
330 (defvar message-exit-actions nil
331   "A list of actions to be performed upon exiting after sending a message.")
332 (defvar message-kill-actions nil
333   "A list of actions to be performed before killing a message buffer.")
334 (defvar message-postpone-actions nil
335   "A list of actions to be performed after postponing a message.")
336
337 ;;;###autoload
338 (defvar message-default-headers nil
339   "*A string containing header lines to be inserted in outgoing messages.
340 It is inserted before you edit the message, so you can edit or delete
341 these lines.")
342
343 ;;;###autoload
344 (defvar message-default-mail-headers nil
345   "*A string of header lines to be inserted in outgoing mails.")
346
347 ;;;###autoload
348 (defvar message-default-news-headers nil
349   "*A string of header lines to be inserted in outgoing news articles.")
350
351 ;; Note: could use /usr/ucb/mail instead of sendmail;
352 ;; options -t, and -v if not interactive.
353 (defvar message-mailer-swallows-blank-line
354   (if (and (string-match "sparc-sun-sunos\\(\\'\\|[^5]\\)" 
355                          system-configuration)
356            (file-readable-p "/etc/sendmail.cf")
357            (let ((buffer (get-buffer-create " *temp*")))
358              (unwind-protect
359                  (save-excursion
360                    (set-buffer buffer)
361                    (insert-file-contents "/etc/sendmail.cf")
362                    (goto-char (point-min))
363                    (let ((case-fold-search nil))
364                      (re-search-forward "^OR\\>" nil t)))
365                (kill-buffer buffer))))
366       ;; According to RFC822, "The field-name must be composed of printable
367       ;; ASCII characters (i.e. characters that have decimal values between
368       ;; 33 and 126, except colon)", i.e. any chars except ctl chars,
369       ;; space, or colon.
370       '(looking-at "[ \t]\\|[][!\"#$%&'()*+,-./0-9;<=>?@A-Z\\\\^_`a-z{|}~]+:"))
371   "Set this non-nil if the system's mailer runs the header and body together.
372 \(This problem exists on Sunos 4 when sendmail is run in remote mode.)
373 The value should be an expression to test whether the problem will
374 actually occur.")
375
376 (defvar message-mode-syntax-table 
377   (let ((table (copy-syntax-table text-mode-syntax-table)))
378     (modify-syntax-entry ?% ". " table)
379     table)
380   "Syntax table used while in Message mode.")
381
382 (defvar message-mode-abbrev-table text-mode-abbrev-table
383   "Abbrev table used in Message mode buffers.
384 Defaults to `text-mode-abbrev-table'.")
385
386 (defvar message-font-lock-keywords
387   (let* ((cite-prefix "A-Za-z") (cite-suffix (concat cite-prefix "0-9_.@-")))
388     (list '("^To:" . font-lock-function-name-face)
389           '("^[GBF]?[Cc][Cc]:\\|^Reply-To:" . font-lock-keyword-face)
390           '("^\\(Subject:\\)[ \t]*\\(.+\\)?"
391             (1 font-lock-comment-face) (2 font-lock-type-face nil t))
392           (list (concat "^\\(" (regexp-quote mail-header-separator) "\\)$")
393                 1 'font-lock-comment-face)
394           (cons (concat "^[ \t]*"
395                         "\\([" cite-prefix "]+[" cite-suffix "]*\\)?"
396                         "[>|}].*")
397                 'font-lock-reference-face)
398           '("^\\(X-[A-Za-z0-9-]+\\|In-reply-to\\):.*"
399             . font-lock-string-face)))
400   "Additional expressions to highlight in Message mode.")
401
402 (defvar message-face-alist
403   '((bold . bold-region)
404     (underline . underline-region)
405     (default . (lambda (b e) 
406                  (unbold-region b e)
407                  (ununderline-region b e))))
408   "Alist of mail and news faces for facemenu.
409 The cdr of ech entry is a function for applying the face to a region.")
410
411 (defvar message-send-hook nil
412   "Hook run before sending messages.")
413
414 (defvar message-sent-hook nil
415   "Hook run after sending messages.")
416
417 ;;; Internal variables.
418
419 (defvar message-buffer-list nil)
420
421 ;; Byte-compiler warning
422 (defvar gnus-active-hashtb)
423
424 ;;; Regexp matching the delimiter of messages in UNIX mail format
425 ;;; (UNIX From lines), minus the initial ^.  
426 (defvar message-unix-mail-delimiter
427   (let ((time-zone-regexp
428          (concat "\\([A-Z]?[A-Z]?[A-Z][A-Z]\\( DST\\)?"
429                  "\\|[-+]?[0-9][0-9][0-9][0-9]"
430                  "\\|"
431                  "\\) *")))
432     (concat
433      "From "
434
435      ;; Username, perhaps with a quoted section that can contain spaces.
436      "\\("
437      "[^ \n]*"
438      "\\(\\|\".*\"[^ \n]*\\)"
439      "\\|<[^<>\n]+>"
440      "\\)  ?"
441
442      ;; The time the message was sent.
443      "\\([^ \n]*\\) *"                  ; day of the week
444      "\\([^ ]*\\) *"                    ; month
445      "\\([0-9]*\\) *"                   ; day of month
446      "\\([0-9:]*\\) *"                  ; time of day
447
448      ;; Perhaps a time zone, specified by an abbreviation, or by a
449      ;; numeric offset.
450      time-zone-regexp
451
452      ;; The year.
453      " [0-9][0-9]\\([0-9]*\\) *"
454
455      ;; On some systems the time zone can appear after the year, too.
456      time-zone-regexp
457
458      ;; Old uucp cruft.
459      "\\(remote from .*\\)?"
460
461      "\n")))
462
463 (defvar message-unsent-separator
464   (concat "^ *---+ +Unsent message follows +---+ *$\\|"
465           "^ *---+ +Returned message +---+ *$\\|"
466           "^Start of returned message$\\|"
467           "^ *---+ +Original message +---+ *$\\|"
468           "^ *--+ +begin message +--+ *$\\|"
469           "^ *---+ +Original message follows +---+ *$\\|"
470           "^|? *---+ +Message text follows: +---+ *|?$")
471   "A regexp that matches the separator before the text of a failed message.")
472
473 (defvar message-header-format-alist 
474   `((Newsgroups)
475     (To . message-fill-address) 
476     (Cc . message-fill-address)
477     (Subject)
478     (In-Reply-To)
479     (Fcc)
480     (Bcc)
481     (Date)
482     (Organization)
483     (Distribution)
484     (Lines)
485     (Expires)
486     (Message-ID)
487     (References . message-fill-header)
488     (X-Mailer)
489     (X-Newsreader))
490   "Alist used for formatting headers.")
491
492 (eval-and-compile
493   (autoload 'message-setup-toolbar "messagexmas")
494   (autoload 'mh-send-letter "mh-comp"))
495
496 \f
497
498 ;;; 
499 ;;; Utility functions.
500 ;;;
501
502 (defun message-point-at-bol ()
503   "Return point at the beginning of the line."
504   (let ((p (point)))
505     (beginning-of-line)
506     (prog1
507         (point)
508       (goto-char p))))
509
510 (defun message-point-at-eol ()
511   "Return point at the end of the line."
512   (let ((p (point)))
513     (end-of-line)
514     (prog1
515         (point)
516       (goto-char p))))
517
518 (defmacro message-y-or-n-p (question show &rest text)
519   "Ask QUESTION, displaying the rest of the arguments in a temp. buffer if SHOW"
520   `(message-talkative-question 'y-or-n-p ,question ,show ,@text))
521
522 ;; Delete the current line (and the next N lines.);
523 (defmacro message-delete-line (&optional n)
524   `(delete-region (progn (beginning-of-line) (point))
525                   (progn (forward-line ,(or n 1)) (point))))
526
527 (defun message-tokenize-header (header &optional separator)
528   "Split HEADER into a list of header elements.
529 \",\" is used as the separator."
530   (let ((regexp (format "[%s]+" (or separator ",")))
531         (beg 1)
532         (first t)
533         quoted elems paren)
534     (save-excursion
535       (message-set-work-buffer)
536       (insert header)
537       (goto-char (point-min))
538       (while (not (eobp))
539         (if first
540             (setq first nil)
541           (forward-char 1))
542         (cond ((and (> (point) beg)
543                     (or (eobp)
544                         (and (looking-at regexp)
545                              (not quoted)
546                              (not paren))))
547                (push (buffer-substring beg (point)) elems)
548                (setq beg (match-end 0)))
549               ((= (following-char) ?\")
550                (setq quoted (not quoted)))
551               ((and (= (following-char) ?\()
552                     (not quoted))
553                (setq paren t))
554               ((and (= (following-char) ?\))
555                     (not quoted))
556                (setq paren nil))))
557       (nreverse elems))))
558
559 (defun message-fetch-field (header)
560   "The same as `mail-fetch-field', only remove all newlines."
561   (let ((value (mail-fetch-field header)))
562     (when value
563       (nnheader-replace-chars-in-string value ?\n ? ))))
564
565 (defun message-fetch-reply-field (header)
566   "Fetch FIELD from the message we're replying to."
567   (when (and message-reply-buffer
568              (buffer-name message-reply-buffer))
569     (save-excursion
570       (set-buffer message-reply-buffer)
571       (message-fetch-field header))))
572
573 (defun message-set-work-buffer ()
574   (if (get-buffer " *message work*")
575       (progn
576         (set-buffer " *message work*")
577         (erase-buffer))
578     (set-buffer (get-buffer-create " *message work*"))
579     (kill-all-local-variables)
580     (buffer-disable-undo (current-buffer))))
581
582 (defun message-functionp (form)
583   "Return non-nil if FORM is funcallable."
584   (or (and (symbolp form) (fboundp form))
585       (and (listp form) (eq (car form) 'lambda))))
586
587 (defun message-strip-subject-re (subject)
588   "Remove \"Re:\" from subject lines."
589   (if (string-match "^[Rr][Ee]: *" subject)
590       (substring subject (match-end 0))
591     subject))
592
593 (defun message-remove-header (header &optional is-regexp first reverse)
594   "Remove HEADER in the narrowed buffer.
595 If REGEXP, HEADER is a regular expression.
596 If FIRST, only remove the first instance of the header.
597 Return the number of headers removed."
598   (goto-char (point-min))
599   (let ((regexp (if is-regexp header (concat "^" header ":")))
600         (number 0)
601         (case-fold-search t)
602         last)
603     (while (and (not (eobp))
604                 (not last))
605       (if (if reverse
606               (not (looking-at regexp))
607             (looking-at regexp))
608           (progn
609             (incf number)
610             (when first
611               (setq last t))
612             (delete-region
613              (point)
614              ;; There might be a continuation header, so we have to search
615              ;; until we find a new non-continuation line.
616              (progn
617                (forward-line 1)
618                (if (re-search-forward "^[^ \t]" nil t)
619                    (goto-char (match-beginning 0))
620                  (point-max)))))
621         (forward-line 1)
622         (if (re-search-forward "^[^ \t]" nil t)
623             (goto-char (match-beginning 0))
624           (point-max))))
625     number))
626
627 (defun message-narrow-to-headers ()
628   "Narrow the buffer to the head of the message."
629   (widen)
630   (narrow-to-region
631    (goto-char (point-min))
632    (if (re-search-forward
633         (concat "^" (regexp-quote mail-header-separator) "\n") nil t)
634        (match-beginning 0)
635      (point-max)))
636   (goto-char (point-min)))
637
638 (defun message-narrow-to-head ()
639   "Narrow the buffer to the head of the message."
640   (widen)
641   (narrow-to-region
642    (goto-char (point-min))
643    (if (search-forward "\n\n" nil 1)
644        (1- (point))
645      (point-max)))
646   (goto-char (point-min)))
647
648 (defun message-news-p ()
649   "Say whether the current buffer contains a news message."
650   (save-excursion
651     (save-restriction
652       (message-narrow-to-headers)
653       (message-fetch-field "newsgroups"))))
654
655 (defun message-mail-p ()
656   "Say whether the current buffer contains a mail message."
657   (save-excursion
658     (save-restriction
659       (message-narrow-to-headers)
660       (or (message-fetch-field "to")
661           (message-fetch-field "cc")
662           (message-fetch-field "bcc")))))
663
664 (defun message-next-header ()
665   "Go to the beginning of the next header."
666   (beginning-of-line)
667   (or (eobp) (forward-char 1))
668   (not (if (re-search-forward "^[^ \t]" nil t)
669            (beginning-of-line)
670          (goto-char (point-max)))))
671     
672 (defun message-sort-headers-1 ()
673   "Sort the buffer as headers using `message-rank' text props."
674   (goto-char (point-min))
675   (sort-subr 
676    nil 'message-next-header 
677    (lambda ()
678      (message-next-header)
679      (unless (bobp)
680        (forward-char -1)))
681    (lambda ()
682      (or (get-text-property (point) 'message-rank)
683          10000))))
684
685 (defun message-sort-headers ()
686   "Sort the headers of the current message according to `message-header-format-alist'."
687   (interactive)
688   (save-excursion
689     (save-restriction
690       (let ((max (1+ (length message-header-format-alist)))
691             rank)
692         (message-narrow-to-headers)
693         (while (re-search-forward "^[^ \n]+:" nil t)
694           (put-text-property
695            (match-beginning 0) (1+ (match-beginning 0))
696            'message-rank
697            (if (setq rank (length (memq (assq (intern (buffer-substring
698                                                        (match-beginning 0)
699                                                        (1- (match-end 0))))
700                                               message-header-format-alist)
701                                         message-header-format-alist)))
702                (- max rank)
703              (1+ max)))))
704       (message-sort-headers-1))))
705
706 \f
707
708 ;;;
709 ;;; Message mode
710 ;;;
711
712 ;;; Set up keymap.
713
714 (defvar message-mode-map nil)
715
716 (unless message-mode-map
717   (setq message-mode-map (copy-keymap text-mode-map))
718   (define-key message-mode-map "\C-c?" 'describe-mode)
719
720   (define-key message-mode-map "\C-c\C-f\C-t" 'message-goto-to)
721   (define-key message-mode-map "\C-c\C-f\C-b" 'message-goto-bcc)
722   (define-key message-mode-map "\C-c\C-f\C-w" 'message-goto-fcc)
723   (define-key message-mode-map "\C-c\C-f\C-c" 'message-goto-cc)
724   (define-key message-mode-map "\C-c\C-f\C-s" 'message-goto-subject)
725   (define-key message-mode-map "\C-c\C-f\C-r" 'message-goto-reply-to)
726   (define-key message-mode-map "\C-c\C-f\C-n" 'message-goto-newsgroups)
727   (define-key message-mode-map "\C-c\C-f\C-d" 'message-goto-distribution)
728   (define-key message-mode-map "\C-c\C-f\C-f" 'message-goto-followup-to)
729   (define-key message-mode-map "\C-c\C-f\C-k" 'message-goto-keywords)
730   (define-key message-mode-map "\C-c\C-f\C-u" 'message-goto-summary)
731   (define-key message-mode-map "\C-c\C-b" 'message-goto-body)
732   (define-key message-mode-map "\C-c\C-i" 'message-goto-signature)
733
734   (define-key message-mode-map "\C-c\C-t" 'message-insert-to)
735   (define-key message-mode-map "\C-c\C-n" 'message-insert-newsgroups)
736   
737   (define-key message-mode-map "\C-c\C-y" 'message-yank-original)
738   (define-key message-mode-map "\C-c\C-q" 'message-fill-yanked-message)
739   (define-key message-mode-map "\C-c\C-w" 'message-insert-signature)
740   (define-key message-mode-map "\C-c\C-r" 'message-caesar-buffer-body)
741   (define-key message-mode-map "\C-c\C-o" 'message-sort-headers)
742   (define-key message-mode-map "\C-c\M-r" 'message-rename-buffer)
743
744   (define-key message-mode-map "\C-c\C-c" 'message-send-and-exit)
745   (define-key message-mode-map "\C-c\C-s" 'message-send)
746   (define-key message-mode-map "\C-c\C-k" 'message-kill-buffer)
747   (define-key message-mode-map "\C-c\C-d" 'message-dont-send)
748
749   (define-key message-mode-map "\t" 'message-tab))
750
751 (easy-menu-define message-mode-menu message-mode-map
752   "Message Menu."
753   '("Message"
754     "Go to Field:"
755     "----"
756     ["To" message-goto-to t]
757     ["Subject" message-goto-subject t]
758     ["Cc" message-goto-cc t]
759     ["Reply-to" message-goto-reply-to t]
760     ["Summary" message-goto-summary t]
761     ["Keywords" message-goto-keywords t]
762     ["Newsgroups" message-goto-newsgroups t]
763     ["Followup-To" message-goto-followup-to t]
764     ["Distribution" message-goto-distribution t]
765     ["Body" message-goto-body t]
766     ["Signature" message-goto-signature t]
767     "----"
768     "Miscellaneous Commands:"
769     "----"
770     ["Sort Headers" message-sort-headers t]
771     ["Yank Original" message-yank-original t]
772     ["Fill Yanked Message" message-fill-yanked-message t]
773     ["Insert Signature" message-insert-signature t]
774     ["Caesar (rot13) Message" message-caesar-buffer-body t]
775     ["Rename buffer" message-rename-buffer t]
776     ["Spellcheck" ispell-message t]
777     "----"
778     ["Send Message" message-send-and-exit t]
779     ["Abort Message" message-dont-send t]))
780
781 (defvar facemenu-add-face-function)
782 (defvar facemenu-remove-face-function)
783
784 ;;;###autoload
785 (defun message-mode ()
786   "Major mode for editing mail and news to be sent.
787 Like Text Mode but with these additional commands:
788 C-c C-s  message-send (send the message)    C-c C-c  message-send-and-exit
789 C-c C-f  move to a header field (and create it if there isn't):
790          C-c C-f C-t  move to To        C-c C-f C-s  move to Subject
791          C-c C-f C-c  move to Cc        C-c C-f C-b  move to Bcc
792          C-c C-f C-f  move to Fcc       C-c C-f C-r  move to Reply-To
793          C-c C-f C-u  move to Summary   C-c C-f C-n  move to Newsgroups
794          C-c C-f C-k  move to Keywords  C-c C-f C-d  move to Distribution
795          C-c C-f C-o  move to Followup-To
796 C-c C-t  message-insert-to (add a To header to a news followup)
797 C-c C-n  message-insert-newsgroups (add a Newsgroup header to a news reply)
798 C-c C-b  message-goto-body (move to beginning of message text).
799 C-c C-i  message-goto-signature (move to the beginning of the signature).
800 C-c C-w  message-insert-signature (insert `message-signature-file' file).
801 C-c C-y  message-yank-original (insert current message, if any).
802 C-c C-q  message-fill-yanked-message (fill what was yanked).
803 C-c C-r  message-ceasar-buffer-body (rot13 the message body)."
804   (interactive)
805   (kill-all-local-variables)
806   (make-local-variable 'message-reply-buffer)
807   (setq message-reply-buffer nil)
808   (make-local-variable 'message-send-actions)
809   (make-local-variable 'message-exit-actions)
810   (make-local-variable 'message-kill-actions)
811   (make-local-variable 'message-postpone-actions)
812   (set-syntax-table message-mode-syntax-table)
813   (use-local-map message-mode-map)
814   (setq local-abbrev-table message-mode-abbrev-table)
815   (setq major-mode 'message-mode)
816   (setq mode-name "Message")
817   (setq buffer-offer-save t)
818   (make-local-variable 'font-lock-defaults)
819   (setq font-lock-defaults '(message-font-lock-keywords t))
820   (make-local-variable 'facemenu-add-face-function)
821   (make-local-variable 'facemenu-remove-face-function)
822   (setq facemenu-add-face-function
823         (lambda (face end)
824           (let ((face-fun (cdr (assq face message-face-alist))))
825             (if face-fun
826                 (funcall face-fun (point) end)
827               (error "Face %s not configured for %s mode" face mode-name)))
828           "")
829         facemenu-remove-face-function t)
830   (make-local-variable 'paragraph-separate)
831   (make-local-variable 'paragraph-start)
832   (setq paragraph-start (concat (regexp-quote mail-header-separator)
833                                 "$\\|[ \t]*[-_][-_][-_]+$\\|"
834                                 "-- $\\|"
835                                 paragraph-start))
836   (setq paragraph-separate (concat (regexp-quote mail-header-separator)
837                                    "$\\|[ \t]*[-_][-_][-_]+$\\|"
838                                    "-- $\\|"
839                                    paragraph-separate))
840   (make-local-variable 'message-reply-headers)
841   (setq message-reply-headers nil)
842   (make-local-variable 'message-newsreader)
843   (make-local-variable 'message-mailer)
844   (make-local-variable 'message-post-method)
845   (make-local-variable 'message-sent-message-via)
846   (setq message-sent-message-via nil)
847   (make-local-variable 'message-checksum)
848   (setq message-checksum nil)
849   ;;(when (fboundp 'mail-hist-define-keys)
850   ;;  (mail-hist-define-keys))
851   (when (string-match "XEmacs\\|Lucid" emacs-version)
852     (message-setup-toolbar))
853   (easy-menu-add message-mode-menu message-mode-map)
854   ;; Allow mail alias things.
855   (if (fboundp 'mail-abbrevs-setup)
856       (mail-abbrevs-setup)
857     (funcall (intern "mail-aliases-setup")))
858   (run-hooks 'text-mode-hook 'message-mode-hook))
859
860 \f
861
862 ;;;
863 ;;; Message mode commands
864 ;;;
865
866 ;;; Movement commands
867
868 (defun message-goto-to ()
869   "Move point to the To header."
870   (interactive)
871   (message-position-on-field "To"))
872
873 (defun message-goto-subject ()
874   "Move point to the Subject header."
875   (interactive)
876   (message-position-on-field "Subject"))
877
878 (defun message-goto-cc ()
879   "Move point to the Cc header."
880   (interactive)
881   (message-position-on-field "Cc" "To"))
882
883 (defun message-goto-bcc ()
884   "Move point to the Bcc  header."
885   (interactive)
886   (message-position-on-field "Bcc" "Cc" "To"))
887
888 (defun message-goto-fcc ()
889   "Move point to the Fcc header."
890   (interactive)
891   (message-position-on-field "Fcc" "To" "Newsgroups"))
892
893 (defun message-goto-reply-to ()
894   "Move point to the Reply-To header."
895   (interactive)
896   (message-position-on-field "Reply-To" "Subject"))
897
898 (defun message-goto-newsgroups ()
899   "Move point to the Newsgroups header."
900   (interactive)
901   (message-position-on-field "Newsgroups"))
902
903 (defun message-goto-distribution ()
904   "Move point to the Distribution header."
905   (interactive)
906   (message-position-on-field "Distribution"))
907
908 (defun message-goto-followup-to ()
909   "Move point to the Followup-To header."
910   (interactive)
911   (message-position-on-field "Followup-To" "Newsgroups"))
912
913 (defun message-goto-keywords ()
914   "Move point to the Keywords header."
915   (interactive)
916   (message-position-on-field "Keywords" "Subject"))
917
918 (defun message-goto-summary ()
919   "Move point to the Summary header."
920   (interactive)
921   (message-position-on-field "Summary" "Subject"))
922
923 (defun message-goto-body ()
924   "Move point to the beginning of the message body."
925   (interactive)
926   (if (looking-at "[ \t]*\n") (expand-abbrev))
927   (goto-char (point-min))
928   (search-forward (concat "\n" mail-header-separator "\n") nil t))
929
930 (defun message-goto-signature ()
931   "Move point to the beginning of the message signature."
932   (interactive)
933   (goto-char (point-min))
934   (or (re-search-forward message-signature-separator nil t)
935       (goto-char (point-max))))
936
937 \f
938
939 (defun message-insert-to ()
940   "Insert a To header that points to the author of the article being replied to."
941   (interactive)
942   (when (and (message-position-on-field "To")
943              (mail-fetch-field "to")
944              (not (string-match "\\` *\\'" (mail-fetch-field "to"))))
945     (insert ", "))
946   (insert (or (message-fetch-reply-field "reply-to")
947               (message-fetch-reply-field "from") "")))
948
949 (defun message-insert-newsgroups ()
950   "Insert the Newsgroups header from the article being replied to."
951   (interactive)
952   (when (and (message-position-on-field "Newsgroups")
953              (mail-fetch-field "newsgroups")
954              (not (string-match "\\` *\\'" (mail-fetch-field "newsgroups"))))
955     (insert ","))
956   (insert (or (message-fetch-reply-field "newsgroups") "")))
957
958 \f
959
960 ;;; Various commands
961
962 (defun message-insert-signature (&optional force)
963   "Insert a signature.  See documentation for the `message-signature' variable."
964   (interactive (list 0))
965   (let* ((signature 
966           (cond ((and (null message-signature)
967                       (eq force 0))
968                  (save-excursion
969                    (goto-char (point-max))
970                    (not (re-search-backward
971                          message-signature-separator nil t))))
972                 ((and (null message-signature)
973                       force)
974                  t)
975                 ((message-functionp message-signature)
976                  (funcall message-signature))
977                 ((listp message-signature)
978                  (eval message-signature))
979                 (t message-signature)))
980          (signature
981           (cond ((stringp signature)
982                  signature)
983                 ((and (eq t signature)
984                       message-signature-file
985                       (file-exists-p message-signature-file))
986                  signature))))
987     (when signature
988       (goto-char (point-max))
989       ;; Insert the signature.
990       (unless (bolp)
991         (insert "\n"))
992       (insert "\n-- \n")
993       (if (eq signature t)
994           (insert-file-contents message-signature-file)
995         (insert signature))
996       (goto-char (point-max))
997       (or (bolp) (insert "\n")))))
998
999 (defvar message-caesar-translation-table nil)
1000
1001 (defun message-caesar-region (b e &optional n)
1002   "Caesar rotation of region by N, default 13, for decrypting netnews."
1003   (interactive
1004    (list
1005     (min (point) (or (mark t) (point)))
1006     (max (point) (or (mark t) (point)))
1007     (when current-prefix-arg
1008       (prefix-numeric-value current-prefix-arg))))
1009
1010   (setq n (if (numberp n) (mod n 26) 13)) ;canonize N
1011   (unless (or (zerop n)                 ; no action needed for a rot of 0
1012               (= b e))                  ; no region to rotate
1013     ;; We build the table, if necessary.
1014     (when (or (not message-caesar-translation-table)
1015               (/= (aref message-caesar-translation-table ?a) (+ ?a n)))
1016       (let ((i -1) 
1017             (table (make-string 256 0)))
1018         (while (< (incf i) 256)
1019           (aset table i i))
1020         (setq table
1021               (concat
1022                (substring table 0 ?A)
1023                (substring table (+ ?A n) (+ ?A n (- 26 n)))
1024                (substring table ?A (+ ?A n))
1025                (substring table (+ ?A 26) ?a)
1026                (substring table (+ ?a n) (+ ?a n (- 26 n)))
1027                (substring table ?a (+ ?a n))
1028                (substring table (+ ?a 26) 255)))
1029         (setq message-caesar-translation-table table)))
1030     ;; Then we translate the region.  Do it this way to retain 
1031     ;; text properties.
1032     (while (< b e)
1033       (subst-char-in-region 
1034        b (1+ b) (char-after b)
1035        (aref message-caesar-translation-table (char-after b)))
1036       (incf b))))
1037
1038 (defun message-caesar-buffer-body (&optional rotnum)
1039   "Caesar rotates all letters in the current buffer by 13 places.
1040 Used to encode/decode possibly offensive messages (commonly in net.jokes).
1041 With prefix arg, specifies the number of places to rotate each letter forward.
1042 Mail and USENET news headers are not rotated."
1043   (interactive (if current-prefix-arg
1044                    (list (prefix-numeric-value current-prefix-arg))
1045                  (list nil)))
1046   (save-excursion
1047     (save-restriction
1048       (when (message-goto-body)
1049         (narrow-to-region (point) (point-max)))
1050       (message-caesar-region (point-min) (point-max) rotnum))))
1051
1052 (defun message-rename-buffer (&optional enter-string)
1053   "Rename the *message* buffer to \"*message* RECIPIENT\".  
1054 If the function is run with a prefix, it will ask for a new buffer
1055 name, rather than giving an automatic name."
1056   (interactive "Pbuffer name: ")
1057   (save-excursion
1058     (save-restriction
1059       (goto-char (point-min))
1060       (narrow-to-region (point) 
1061                         (search-forward mail-header-separator nil 'end))
1062       (let* ((mail-to (if (message-news-p) (message-fetch-field "Newsgroups")
1063                         (message-fetch-field "To")))
1064              (mail-trimmed-to
1065               (if (string-match "," mail-to)
1066                   (concat (substring mail-to 0 (match-beginning 0)) ", ...")
1067                 mail-to))
1068              (name-default (concat "*message* " mail-trimmed-to))
1069              (name (if enter-string
1070                        (read-string "New buffer name: " name-default)
1071                      name-default))
1072              (default-directory
1073                (file-name-as-directory message-autosave-directory)))
1074           (rename-buffer name t)))))
1075
1076 (defun message-fill-yanked-message (&optional justifyp)
1077   "Fill the paragraphs of a message yanked into this one.
1078 Numeric argument means justify as well."
1079   (interactive "P")
1080   (save-excursion
1081     (goto-char (point-min))
1082     (search-forward (concat "\n" mail-header-separator "\n") nil t)
1083     (let ((fill-prefix message-yank-prefix))
1084       (fill-individual-paragraphs (point) (point-max) justifyp t))))
1085
1086 (defun message-indent-citation ()
1087   "Modify text just inserted from a message to be cited.
1088 The inserted text should be the region.
1089 When this function returns, the region is again around the modified text.
1090
1091 Normally, indent each nonblank line `message-indentation-spaces' spaces.
1092 However, if `message-yank-prefix' is non-nil, insert that prefix on each line."
1093   (let ((start (point)))
1094     ;; Remove unwanted headers.
1095     (when message-ignored-cited-headers
1096       (save-restriction
1097         (narrow-to-region 
1098          (goto-char start)
1099          (if (search-forward "\n\n" nil t)
1100              (1- (point))
1101            (point)))
1102         (message-remove-header message-ignored-cited-headers t)
1103         (goto-char (point-max))))
1104     ;; Delete blank lines at the start of the buffer.
1105     (while (and (point-min)
1106                 (eolp))
1107       (message-delete-line))
1108     ;; Delete blank lines at the end of the buffer.
1109     (goto-char (point-max))
1110     (unless (eolp)
1111       (insert "\n"))
1112     (while (and (zerop (forward-line -1))
1113                 (looking-at "$"))
1114       (message-delete-line))
1115     ;; Do the indentation.
1116     (if (null message-yank-prefix)
1117         (indent-rigidly start (mark t) message-indentation-spaces)
1118       (save-excursion
1119         (goto-char start)
1120         (while (< (point) (mark t))
1121           (insert message-yank-prefix)
1122           (forward-line 1)))
1123       (goto-char start))))
1124
1125 (defun message-yank-original (&optional arg)
1126   "Insert the message being replied to, if any.
1127 Puts point before the text and mark after.
1128 Normally indents each nonblank line ARG spaces (default 3).  However,
1129 if `message-yank-prefix' is non-nil, insert that prefix on each line.
1130
1131 This function uses `message-cite-function' to do the actual citing.
1132
1133 Just \\[universal-argument] as argument means don't indent, insert no
1134 prefix, and don't delete any headers."
1135   (interactive "P")
1136   (let ((modified (buffer-modified-p)))
1137     (when (and message-reply-buffer
1138                message-cite-function)
1139       (delete-windows-on message-reply-buffer t)
1140       (insert-buffer message-reply-buffer)
1141       (funcall message-cite-function)
1142       (message-exchange-point-and-mark)
1143       (unless (bolp)
1144         (insert ?\n))
1145       (unless modified
1146         (setq message-checksum (cons (message-checksum) (buffer-size)))))))
1147
1148 (defun message-cite-original ()
1149   "Cite function in the standard Message manner."
1150   (let ((start (point))
1151         (functions 
1152          (when message-indent-citation-function
1153            (if (listp message-indent-citation-function)
1154                message-indent-citation-function
1155              (list message-indent-citation-function)))))
1156     (goto-char start)
1157     (while functions
1158       (funcall (pop functions)))
1159     (when message-citation-line-function
1160       (unless (bolp)
1161         (insert "\n"))
1162       (funcall message-citation-line-function))))
1163
1164 (defun message-insert-citation-line ()
1165   "Function that inserts a simple citation line."
1166   (when message-reply-headers
1167     (insert (mail-header-from message-reply-headers) " writes:\n\n")))
1168
1169 (defun message-position-on-field (header &rest afters)
1170   (let ((case-fold-search t))
1171     (save-restriction
1172       (narrow-to-region
1173        (goto-char (point-min))
1174        (progn
1175          (re-search-forward 
1176           (concat "^" (regexp-quote mail-header-separator) "$"))
1177          (match-beginning 0)))
1178       (goto-char (point-min))
1179       (if (re-search-forward (concat "^" (regexp-quote header) ":") nil t)
1180           (progn
1181             (re-search-forward "^[^ \t]" nil 'move)
1182             (beginning-of-line)
1183             (skip-chars-backward "\n")
1184             t)
1185         (while (and afters
1186                     (not (re-search-forward 
1187                           (concat "^" (regexp-quote (car afters)) ":")
1188                           nil t)))
1189           (pop afters))
1190         (when afters
1191           (re-search-forward "^[^ \t]" nil 'move)
1192           (beginning-of-line))
1193         (insert header ": \n")
1194         (forward-char -1)
1195         nil))))
1196
1197 (defun message-remove-signature ()
1198   "Remove the signature from the text between point and mark.
1199 The text will also be indented the normal way."
1200   (save-excursion
1201     (let ((start (point))
1202           mark)
1203     (if (not (re-search-forward message-signature-separator (mark t) t))
1204         ;; No signature here, so we just indent the cited text.
1205         (message-indent-citation)
1206       ;; Find the last non-empty line.
1207       (forward-line -1)
1208       (while (looking-at "[ \t]*$")
1209         (forward-line -1))
1210       (forward-line 1)
1211       (setq mark (set-marker (make-marker) (point)))
1212       (goto-char start)
1213       (message-indent-citation)
1214       ;; Enable undoing the deletion.
1215       (undo-boundary)
1216       (delete-region mark (mark t))
1217       (set-marker mark nil)))))
1218
1219 \f
1220
1221 ;;;
1222 ;;; Sending messages
1223 ;;;
1224
1225 (defun message-send-and-exit (&optional arg)
1226   "Send message like `message-send', then, if no errors, exit from mail buffer."
1227   (interactive "P")
1228   (let ((buf (current-buffer))
1229         (actions message-exit-actions))
1230     (when (and (message-send arg)
1231                (buffer-name buf))
1232       (if message-kill-buffer-on-exit
1233           (kill-buffer buf)
1234         (bury-buffer buf)
1235         (when (eq buf (current-buffer))
1236           (message-bury buf)))
1237       (message-do-actions actions))))
1238
1239 (defun message-dont-send ()
1240   "Don't send the message you have been editing."
1241   (interactive)
1242   (message-bury (current-buffer))
1243   (message-do-actions message-postpone-actions))
1244
1245 (defun message-kill-buffer ()
1246   "Kill the current buffer."
1247   (interactive)
1248   (let ((actions message-kill-actions))
1249     (kill-buffer (current-buffer))
1250     (message-do-actions actions)))
1251
1252 (defun message-bury (buffer)
1253   "Bury this mail buffer."
1254   (let ((newbuf (other-buffer buffer)))
1255     (bury-buffer buffer)
1256     (if (and (fboundp 'frame-parameters)
1257              (cdr (assq 'dedicated (frame-parameters)))
1258              (not (null (delq (selected-frame) (visible-frame-list)))))
1259         (delete-frame (selected-frame))
1260       (switch-to-buffer newbuf))))
1261
1262 (defun message-send (&optional arg)
1263   "Send the message in the current buffer.
1264 If `message-interactive' is non-nil, wait for success indication
1265 or error messages, and inform user.
1266 Otherwise any failure is reported in a message back to
1267 the user from the mailer."
1268   (interactive "P")
1269   (when (if buffer-file-name
1270             (y-or-n-p (format "Send buffer contents as %s message? "
1271                               (if (message-mail-p)
1272                                   (if (message-news-p) "mail and news" "mail")
1273                                 "news")))
1274           (or (buffer-modified-p)
1275               (y-or-n-p "No changes in the buffer; really send? ")))
1276     ;; Make it possible to undo the coming changes.
1277     (undo-boundary)
1278     (let ((inhibit-read-only t))
1279       (put-text-property (point-min) (point-max) 'read-only nil))
1280     (message-fix-before-sending)
1281     (run-hooks 'message-send-hook)
1282     (message "Sending...")
1283     (when (and (or (not (message-news-p))
1284                    (and (or (not (memq 'news message-sent-message-via))
1285                             (y-or-n-p
1286                              "Already sent message via news; resend? "))
1287                         (funcall message-send-news-function arg)))
1288                (or (not (message-mail-p))
1289                    (and (or (not (memq 'mail message-sent-message-via))
1290                             (y-or-n-p
1291                              "Already sent message via mail; resend? "))
1292                         (message-send-mail arg))))
1293       (message-do-fcc)
1294       ;;(when (fboundp 'mail-hist-put-headers-into-history)
1295       ;; (mail-hist-put-headers-into-history))
1296       (run-hooks 'message-sent-hook)
1297       (message "Sending...done")
1298       ;; If buffer has no file, mark it as unmodified and delete autosave.
1299       (unless buffer-file-name
1300         (set-buffer-modified-p nil)
1301         (delete-auto-save-file-if-necessary t))
1302       ;; Delete other mail buffers and stuff.
1303       (message-do-send-housekeeping)
1304       (message-do-actions message-send-actions)
1305       ;; Return success.
1306       t)))
1307
1308 (defun message-fix-before-sending ()
1309   "Do various things to make the message nice before sending it."
1310   ;; Make sure there's a newline at the end of the message.
1311   (goto-char (point-max))
1312   (unless (bolp)
1313     (insert "\n")))
1314
1315 (defun message-add-action (action &rest types)
1316   "Add ACTION to be performed when doing an exit of type TYPES."
1317   (let (var)
1318     (while types
1319       (set (setq var (intern (format "message-%s-actions" (pop types))))
1320            (nconc (symbol-value var) (list action))))))
1321
1322 (defun message-do-actions (actions)
1323   "Perform all actions in ACTIONS."
1324   ;; Now perform actions on successful sending.
1325   (while actions
1326     (condition-case nil
1327         (cond 
1328          ;; A simple function.
1329          ((message-functionp (car actions))
1330           (funcall (car actions)))
1331          ;; Something to be evaled.
1332          (t
1333           (eval (car actions))))
1334       (error))
1335     (pop actions)))
1336
1337 (defun message-send-mail (&optional arg)
1338   (require 'mail-utils)
1339   (let ((tembuf (generate-new-buffer " message temp"))
1340         (case-fold-search nil)
1341         (news (message-news-p))
1342         (mailbuf (current-buffer)))
1343     (save-restriction
1344       (message-narrow-to-headers)
1345       ;; Insert some headers.
1346       (let ((message-deletable-headers
1347              (if news nil message-deletable-headers)))
1348         (message-generate-headers message-required-mail-headers))
1349       ;; Let the user do all of the above.
1350       (run-hooks 'message-header-hook))
1351     (unwind-protect
1352         (save-excursion
1353           (set-buffer tembuf)
1354           (erase-buffer)
1355           ;; Avoid copying text props.
1356           (insert (format 
1357                    "%s" (save-excursion
1358                           (set-buffer mailbuf)
1359                           (buffer-string))))
1360           ;; Remove some headers.
1361           (save-restriction
1362             (message-narrow-to-headers)
1363             ;; Remove some headers.
1364             (message-remove-header message-ignored-mail-headers t))
1365           (goto-char (point-max))
1366           ;; require one newline at the end.
1367           (or (= (preceding-char) ?\n)
1368               (insert ?\n))
1369           (when (and news
1370                      (or (message-fetch-field "cc")
1371                          (message-fetch-field "to")))
1372             (message-insert-courtesy-copy))
1373           (funcall message-send-mail-function))
1374       (kill-buffer tembuf))
1375     (set-buffer mailbuf)
1376     (push 'mail message-sent-message-via)))
1377
1378 (defun message-send-mail-with-sendmail ()
1379   "Send off the prepared buffer with sendmail."
1380   (let ((errbuf (if message-interactive
1381                     (generate-new-buffer " sendmail errors")
1382                   0))
1383         resend-to-addresses delimline)
1384     (let ((case-fold-search t))
1385       (save-restriction
1386         (message-narrow-to-headers)
1387         (setq resend-to-addresses (message-fetch-field "resent-to")))
1388       ;; Change header-delimiter to be what sendmail expects.
1389       (goto-char (point-min))
1390       (re-search-forward
1391        (concat "^" (regexp-quote mail-header-separator) "\n"))
1392       (replace-match "\n")
1393       (backward-char 1)
1394       (setq delimline (point-marker))
1395       ;; Insert an extra newline if we need it to work around
1396       ;; Sun's bug that swallows newlines.
1397       (goto-char (1+ delimline))
1398       (when (eval message-mailer-swallows-blank-line)
1399         (newline))
1400       (when message-interactive
1401         (save-excursion
1402           (set-buffer errbuf)
1403           (erase-buffer))))
1404     (let ((default-directory "/"))
1405       (apply 'call-process-region
1406              (append (list (point-min) (point-max)
1407                            (if (boundp 'sendmail-program)
1408                                sendmail-program
1409                              "/usr/lib/sendmail")
1410                            nil errbuf nil "-oi")
1411                      ;; Always specify who from,
1412                      ;; since some systems have broken sendmails.
1413                      (list "-f" (user-login-name))
1414                      ;; These mean "report errors by mail"
1415                      ;; and "deliver in background".
1416                      (if (null message-interactive) '("-oem" "-odb"))
1417                      ;; Get the addresses from the message
1418                      ;; unless this is a resend.
1419                      ;; We must not do that for a resend
1420                      ;; because we would find the original addresses.
1421                      ;; For a resend, include the specific addresses.
1422                      (if resend-to-addresses
1423                          (list resend-to-addresses)
1424                        '("-t")))))
1425     (when message-interactive
1426       (save-excursion
1427         (set-buffer errbuf)
1428         (goto-char (point-min))
1429         (while (re-search-forward "\n\n* *" nil t)
1430           (replace-match "; "))
1431         (if (not (zerop (buffer-size)))
1432             (error "Sending...failed to %s"
1433                    (buffer-substring (point-min) (point-max)))))
1434       (when (bufferp errbuf)
1435         (kill-buffer errbuf)))))
1436
1437 (defun message-send-mail-with-mh ()
1438   "Send the prepared message buffer with mh."
1439   (let ((mh-previous-window-config nil)
1440         (name (make-temp-name
1441                (concat (file-name-as-directory message-autosave-directory)
1442                        "msg."))))
1443     (setq buffer-file-name name)
1444     ;; MH wants to generate these headers itself.
1445     (let ((headers message-deletable-headers))
1446       (while headers
1447         (goto-char (point-min)) 
1448         ;;(message "Deleting header %s" (car headers)) (sit-for 5)
1449         (and (re-search-forward 
1450               (concat "^" (symbol-name (car headers)) ": *") nil t)
1451              (message-delete-line))
1452         (pop headers)))
1453     ;; Pass it on to mh.
1454     (mh-send-letter)))
1455
1456 (defun message-send-news (&optional arg)
1457   (let ((tembuf (generate-new-buffer " *message temp*"))
1458         (case-fold-search nil)
1459         (method (if (message-functionp message-post-method)
1460                     (funcall message-post-method arg)
1461                   message-post-method))
1462         (messbuf (current-buffer))
1463         (message-syntax-checks
1464          (if arg
1465              (cons '(existing-newsgroups . disabled)
1466                    message-syntax-checks)
1467            message-syntax-checks))
1468         result)
1469     (save-restriction
1470       (message-narrow-to-headers)
1471       ;; Insert some headers.
1472       (message-generate-headers message-required-news-headers)
1473       ;; Let the user do all of the above.
1474       (run-hooks 'message-header-hook))
1475     (message-cleanup-headers)
1476     (when (message-check-news-syntax)
1477       (unwind-protect
1478           (save-excursion
1479             (set-buffer tembuf)
1480             (buffer-disable-undo (current-buffer))
1481             (erase-buffer) 
1482             ;; Avoid copying text props.
1483           (insert (format 
1484                    "%s" (save-excursion
1485                           (set-buffer messbuf)
1486                           (buffer-string))))
1487             ;; Remove some headers.
1488             (save-restriction
1489               (message-narrow-to-headers)
1490               ;; Remove some headers.
1491               (message-remove-header message-ignored-news-headers t))
1492             (goto-char (point-max))
1493             ;; require one newline at the end.
1494             (or (= (preceding-char) ?\n)
1495                 (insert ?\n))
1496             (let ((case-fold-search t))
1497               ;; Remove the delimeter.
1498               (goto-char (point-min))
1499               (re-search-forward
1500                (concat "^" (regexp-quote mail-header-separator) "\n"))
1501               (replace-match "\n")
1502               (backward-char 1))
1503             (require (car method))
1504             (funcall (intern (format "%s-open-server" (car method)))
1505                      (cadr method) (cddr method))
1506             (setq result
1507                   (funcall (intern (format "%s-request-post" (car method))))))
1508         (kill-buffer tembuf))
1509       (set-buffer messbuf)
1510       (if result
1511           (push 'news message-sent-message-via)
1512         (message "Couldn't send message via news: %s"
1513                  (nnheader-get-report (car method)))
1514         nil))))
1515
1516 ;;;
1517 ;;; Header generation & syntax checking.
1518 ;;;
1519
1520 (defun message-check-news-syntax ()
1521   "Check the syntax of the message."
1522   (and 
1523    ;; We narrow to the headers and check them first.
1524    (save-excursion
1525      (save-restriction
1526        (message-narrow-to-headers)
1527        (and 
1528         ;; Check for commands in Subject.
1529         (or 
1530          (message-check-element 'subject-cmsg)
1531          (save-excursion
1532            (if (string-match "^cmsg " (message-fetch-field "subject"))
1533                (y-or-n-p
1534                 "The control code \"cmsg \" is in the subject. Really post? ")
1535              t)))
1536         ;; Check for multiple identical headers.
1537         (or (message-check-element 'multiple-headers)
1538             (save-excursion
1539               (let (found)
1540                 (while (and (not found) 
1541                             (re-search-forward "^[^ \t:]+: " nil t))
1542                   (save-excursion
1543                     (or (re-search-forward 
1544                          (concat "^" (setq found
1545                                            (buffer-substring 
1546                                             (match-beginning 0) 
1547                                             (- (match-end 0) 2))))
1548                          nil t)
1549                         (setq found nil))))
1550                 (if found
1551                     (y-or-n-p 
1552                      (format "Multiple %s headers. Really post? " found))
1553                   t))))
1554         ;; Check for Version and Sendsys.
1555         (or (message-check-element 'sendsys)
1556             (save-excursion
1557               (if (re-search-forward "^Sendsys:\\|^Version:" nil t)
1558                   (y-or-n-p
1559                    (format "The article contains a %s command. Really post? "
1560                            (buffer-substring (match-beginning 0) 
1561                                              (1- (match-end 0)))))
1562                 t)))
1563         ;; See whether we can shorten Followup-To.
1564         (or (message-check-element 'shorten-followup-to)
1565             (let ((newsgroups (message-fetch-field "newsgroups"))
1566                   (followup-to (message-fetch-field "followup-to"))
1567                   to)
1568               (when (and newsgroups (string-match "," newsgroups)
1569                          (not followup-to)
1570                          (not
1571                           (zerop
1572                            (length
1573                             (setq to (completing-read 
1574                                       "Followups to: (default all groups) " 
1575                                       (mapcar (lambda (g) (list g))
1576                                               (cons "poster" 
1577                                                     (message-tokenize-header 
1578                                                      newsgroups)))))))))
1579                 (goto-char (point-min))
1580                 (insert "Followup-To: " to "\n"))
1581               t))
1582         ;; Check "Shoot me".
1583         (or (message-check-element 'shoot)
1584             (save-excursion
1585               (if (re-search-forward
1586                    "Message-ID.*.i-have-a-misconfigured-system-so-shoot-me"
1587                    nil t)
1588                   (y-or-n-p
1589                    "You appear to have a misconfigured system.  Really post? ")
1590                 t)))
1591         ;; Check for Approved.
1592         (or (message-check-element 'approved)
1593             (save-excursion
1594               (if (re-search-forward "^Approved:" nil t)
1595                   (y-or-n-p
1596                    "The article contains an Approved header. Really post? ")
1597                 t)))
1598         ;; Check the Message-Id header.
1599         (or (message-check-element 'message-id)
1600             (save-excursion
1601               (let* ((case-fold-search t)
1602                      (message-id (message-fetch-field "message-id")))
1603                 (or (not message-id)
1604                     (and (string-match "@" message-id)
1605                          (string-match "@[^\\.]*\\." message-id))
1606                     (y-or-n-p
1607                      (format 
1608                       "The Message-ID looks strange: \"%s\". Really post? "
1609                       message-id))))))
1610         ;; Check the Subject header.
1611         (or 
1612          (message-check-element 'subject)
1613          (save-excursion
1614            (let* ((case-fold-search t)
1615                   (subject (message-fetch-field "subject")))
1616              (or
1617               (and subject
1618                    (not (string-match "\\`[ \t]*\\'" subject)))
1619               (progn
1620                 (message 
1621                  "The subject field is empty or missing.  Posting is denied.")
1622                 nil)))))
1623         ;; Check the Newsgroups & Followup-To headers.
1624         (or
1625          (message-check-element 'existing-newsgroups)
1626          (let* ((case-fold-search t)
1627                 (newsgroups (message-fetch-field "newsgroups"))
1628                 (followup-to (message-fetch-field "followup-to"))
1629                 (groups (message-tokenize-header
1630                          (if followup-to
1631                              (concat newsgroups "," followup-to)
1632                            newsgroups)))
1633                 (hashtb (and (boundp 'gnus-active-hashtb)
1634                              gnus-active-hashtb))
1635                 errors)
1636            (if (not hashtb)
1637                t
1638              (while groups
1639                (when (and (not (boundp (intern (car groups) hashtb)))
1640                           (not (equal (car groups) "poster")))
1641                  (push (car groups) errors))
1642                (pop groups))
1643              (if (not errors)
1644                  t
1645                (y-or-n-p
1646                 (format
1647                  "Really post to %s unknown group%s: %s "
1648                  (if (= (length errors) 1) "this" "these")
1649                  (if (= (length errors) 1) "" "s")
1650                  (mapconcat 'identity errors ", ")))))))
1651         ;; Check the Newsgroups & Followup-To headers for syntax errors.
1652         (or
1653          (message-check-element 'valid-newsgroups)
1654          (let ((case-fold-search t)
1655                (headers '("Newsgroups" "Followup-To"))
1656                header error)
1657            (while (and headers (not error))
1658              (when (setq header (mail-fetch-field (car headers)))
1659                (if (or
1660                     (not 
1661                      (string-match
1662                       "\\`\\([-+_&.a-zA-Z0-9]+\\)?\\(,[-.a-zA-Z0-9]+\\)*\\'"
1663                       header))
1664                     (memq 
1665                      nil (mapcar 
1666                           (lambda (g)
1667                             (not (string-match "\\.\\'\\|\\.\\." g)))
1668                           (message-tokenize-header header ","))))
1669                    (setq error t)))
1670              (unless error
1671                (pop headers)))
1672            (if (not error)
1673                t
1674              (y-or-n-p
1675               (format "The %s header looks odd: \"%s\".  Really post? "
1676                       (car headers) header)))))
1677         ;; Check the From header.
1678         (or 
1679          (save-excursion
1680            (let* ((case-fold-search t)
1681                   (from (message-fetch-field "from")))
1682              (cond
1683               ((not from)
1684                (message "There is no From line.  Posting is denied.")
1685                nil)
1686               ((not (string-match "@[^\\.]*\\." from))
1687                (message
1688                 "Denied posting -- the From looks strange: \"%s\"." from)
1689                nil)
1690               ((string-match "@[^@]*@" from)
1691                (message 
1692                 "Denied posting -- two \"@\"'s in the From header: %s." from)
1693                nil)
1694               ((string-match "(.*).*(.*)" from)
1695                (message
1696                 "Denied posting -- the From header looks strange: \"%s\"." 
1697                 from)
1698                nil)
1699               (t t))))))))
1700    ;; Check for long lines.
1701    (or (message-check-element 'long-lines)
1702        (save-excursion
1703          (goto-char (point-min))
1704          (re-search-forward
1705           (concat "^" (regexp-quote mail-header-separator) "$"))
1706          (while (and
1707                  (progn
1708                    (end-of-line)
1709                    (< (current-column) 80))
1710                  (zerop (forward-line 1))))
1711          (or (bolp)
1712              (eobp)
1713              (y-or-n-p
1714               "You have lines longer than 79 characters.  Really post? "))))
1715    ;; Check whether the article is empty.
1716    (or (message-check-element 'empty)
1717        (save-excursion
1718          (goto-char (point-min))
1719          (re-search-forward
1720           (concat "^" (regexp-quote mail-header-separator) "$"))
1721          (forward-line 1)
1722          (let ((b (point)))
1723            (goto-char (point-max))
1724            (re-search-backward message-signature-separator nil t)
1725            (beginning-of-line)
1726            (or (re-search-backward "[^ \n\t]" b t)
1727                (y-or-n-p "Empty article.  Really post? ")))))
1728    ;; Check for control characters.
1729    (or (message-check-element 'control-chars)
1730        (save-excursion
1731          (if (re-search-forward "[\000-\007\013\015-\037\200-\237]" nil t)
1732              (y-or-n-p 
1733               "The article contains control characters. Really post? ")
1734            t)))
1735    ;; Check excessive size.
1736    (or (message-check-element 'size)
1737        (if (> (buffer-size) 60000)
1738            (y-or-n-p
1739             (format "The article is %d octets long. Really post? "
1740                     (buffer-size)))
1741          t))
1742    ;; Check whether any new text has been added.
1743    (or (message-check-element 'new-text)
1744        (not message-checksum)
1745        (not (and (eq (message-checksum) (car message-checksum))
1746                  (eq (buffer-size) (cdr message-checksum))))
1747        (y-or-n-p
1748         "It looks like no new text has been added.  Really post? "))
1749    ;; Check the length of the signature.
1750    (or
1751     (message-check-element 'signature)
1752     (progn
1753       (goto-char (point-max))
1754       (if (or (not (re-search-backward message-signature-separator nil t))
1755               (search-forward message-forward-end-separator nil t))
1756           t
1757         (if (> (count-lines (point) (point-max)) 5)
1758             (y-or-n-p
1759              (format
1760               "Your .sig is %d lines; it should be max 4.  Really post? "
1761               (1- (count-lines (point) (point-max)))))
1762           t))))))
1763
1764 (defun message-check-element (type)
1765   "Returns non-nil if this type is not to be checked."
1766   (if (eq message-syntax-checks 'dont-check-for-anything-just-trust-me)
1767       t
1768     (let ((able (assq type message-syntax-checks)))
1769       (and (consp able)
1770            (eq (cdr able) 'disabled)))))
1771
1772 (defun message-checksum ()
1773   "Return a \"checksum\" for the current buffer."
1774   (let ((sum 0))
1775     (save-excursion
1776       (goto-char (point-min))
1777       (re-search-forward
1778        (concat "^" (regexp-quote mail-header-separator) "$"))
1779       (while (not (eobp))
1780         (when (not (looking-at "[ \t\n]"))
1781           (setq sum (logxor (ash sum 1) (following-char))))
1782         (forward-char 1)))
1783     sum))
1784
1785 (defun message-do-fcc ()
1786   "Process Fcc headers in the current buffer."
1787   (let ((case-fold-search t)
1788         (buf (current-buffer))
1789         list file)
1790     (save-excursion
1791       (set-buffer (get-buffer-create " *message temp*"))
1792       (buffer-disable-undo (current-buffer))
1793       (erase-buffer)
1794       (insert-buffer-substring buf)
1795       (save-restriction
1796         (message-narrow-to-headers)
1797         (while (setq file (message-fetch-field "fcc"))
1798           (push file list)
1799           (message-remove-header "fcc" nil t)))
1800       (goto-char (point-min))
1801       (re-search-forward (concat "^" (regexp-quote mail-header-separator) "$"))
1802       (replace-match "" t t)
1803       ;; Process FCC operations.
1804       (while list
1805         (setq file (pop list))
1806         (if (string-match "^[ \t]*|[ \t]*\\(.*\\)[ \t]*$" file)
1807             ;; Pipe the article to the program in question.
1808             (call-process-region (point-min) (point-max) shell-file-name
1809                                  nil nil nil shell-command-switch
1810                                  (match-string 1 file))
1811           ;; Save the article.
1812           (setq file (expand-file-name file))
1813           (unless (file-exists-p (file-name-directory file))
1814             (make-directory (file-name-directory file) t))
1815           (if (and message-fcc-handler-function
1816                    (not (eq message-fcc-handler-function 'rmail-output)))
1817               (funcall message-fcc-handler-function file)
1818             (if (and (file-readable-p file) (mail-file-babyl-p file))
1819                 (rmail-output file 1)
1820               (let ((mail-use-rfc822 t))
1821                 (rmail-output file 1 t t))))))
1822       (kill-buffer (current-buffer)))))
1823
1824 (defun message-cleanup-headers ()
1825   "Do various automatic cleanups of the headers."
1826   ;; Remove empty lines in the header.
1827   (save-restriction
1828     (message-narrow-to-headers)
1829     (while (re-search-forward "^[ \t]*\n" nil t)
1830       (replace-match "" t t)))
1831
1832   ;; Correct Newsgroups and Followup-To headers: change sequence of
1833   ;; spaces to comma and eliminate spaces around commas.  Eliminate
1834   ;; embedded line breaks.
1835   (goto-char (point-min))
1836   (while (re-search-forward "^\\(Newsgroups\\|Followup-To\\): +" nil t)
1837     (save-restriction
1838       (narrow-to-region
1839        (point)
1840        (if (re-search-forward "^[^ \t]" nil t)
1841            (match-beginning 0)
1842          (forward-line 1)
1843          (point)))
1844       (goto-char (point-min))
1845       (while (re-search-forward "\n[ \t]+" nil t)
1846         (replace-match " " t t))        ;No line breaks (too confusing)
1847       (goto-char (point-min))
1848       (while (re-search-forward "[ \t\n]*,[ \t\n]*\\|[ \t]+" nil t)
1849         (replace-match "," t t))
1850       (goto-char (point-min))
1851       ;; Remove trailing commas.
1852       (when (re-search-forward ",+$" nil t)
1853         (replace-match "" t t)))))
1854
1855 (defun message-make-date ()
1856   "Make a valid data header."
1857   (let ((now (current-time)))
1858     (timezone-make-date-arpa-standard 
1859      (current-time-string now) (current-time-zone now))))
1860
1861 (defun message-make-message-id ()
1862   "Make a unique Message-ID."
1863   (concat "<" (message-unique-id) 
1864           (let ((psubject (save-excursion (message-fetch-field "subject"))))
1865             (if (and message-reply-headers
1866                      (mail-header-references message-reply-headers)
1867                      (mail-header-subject message-reply-headers)
1868                      psubject
1869                      (mail-header-subject message-reply-headers)
1870                      (not (string= 
1871                            (message-strip-subject-re
1872                             (mail-header-subject message-reply-headers))
1873                            (message-strip-subject-re psubject))))
1874                 "_-_" ""))
1875           "@" (message-make-fqdn) ">"))
1876
1877 (defvar message-unique-id-char nil)
1878
1879 ;; If you ever change this function, make sure the new version
1880 ;; cannot generate IDs that the old version could.
1881 ;; You might for example insert a "." somewhere (not next to another dot
1882 ;; or string boundary), or modify the "fsf" string.
1883 (defun message-unique-id ()
1884   ;; Don't use microseconds from (current-time), they may be unsupported.
1885   ;; Instead we use this randomly inited counter.
1886   (setq message-unique-id-char
1887         (% (1+ (or message-unique-id-char (logand (random t) (1- (lsh 1 20)))))
1888            ;; (current-time) returns 16-bit ints,
1889            ;; and 2^16*25 just fits into 4 digits i base 36.
1890            (* 25 25)))
1891   (let ((tm (current-time)))
1892     (concat
1893      (if (memq system-type '(ms-dos emx vax-vms))
1894          (let ((user (downcase (user-login-name))))
1895            (while (string-match "[^a-z0-9_]" user)
1896              (aset user (match-beginning 0) ?_))
1897            user)
1898        (message-number-base36 (user-uid) -1))
1899      (message-number-base36 (+ (car   tm) 
1900                                (lsh (% message-unique-id-char 25) 16)) 4)
1901      (message-number-base36 (+ (nth 1 tm)
1902                                (lsh (/ message-unique-id-char 25) 16)) 4)
1903      ;; Append the newsreader name, because while the generated
1904      ;; ID is unique to this newsreader, other newsreaders might
1905      ;; otherwise generate the same ID via another algorithm.
1906      ".fsf")))
1907
1908 (defun message-number-base36 (num len)
1909   (if (if (< len 0) (<= num 0) (= len 0))
1910       ""
1911     (concat (message-number-base36 (/ num 36) (1- len))
1912             (char-to-string (aref "zyxwvutsrqponmlkjihgfedcba9876543210"
1913                                   (% num 36))))))
1914
1915 (defun message-make-organization ()
1916   "Make an Organization header."
1917   (let* ((organization 
1918           (or (getenv "ORGANIZATION")
1919               (when message-user-organization
1920                 (if (message-functionp message-user-organization)
1921                     (funcall message-user-organization)
1922                   message-user-organization)))))
1923     (save-excursion
1924       (message-set-work-buffer)
1925       (cond ((stringp organization)
1926              (insert organization))
1927             ((and (eq t organization)
1928                   message-user-organization-file
1929                   (file-exists-p message-user-organization-file))
1930              (insert-file-contents message-user-organization-file)))
1931       (goto-char (point-min))
1932       (while (re-search-forward "[\t\n]+" nil t)
1933         (replace-match "" t t))
1934       (unless (zerop (buffer-size))
1935         (buffer-string)))))
1936
1937 (defun message-make-lines ()
1938   "Count the number of lines and return numeric string."
1939   (save-excursion
1940     (save-restriction
1941       (widen)
1942       (goto-char (point-min))
1943       (re-search-forward 
1944        (concat "^" (regexp-quote mail-header-separator) "$"))
1945       (forward-line 1)
1946       (int-to-string (count-lines (point) (point-max))))))
1947
1948 (defun message-make-in-reply-to ()
1949   "Return the In-Reply-To header for this message."
1950   (when message-reply-headers
1951     (let ((from (mail-header-from message-reply-headers))
1952           (date (mail-header-date message-reply-headers)))
1953       (when from
1954         (let ((stop-pos 
1955                (string-match "  *at \\|  *@ \\| *(\\| *<" from)))
1956           (concat (if stop-pos (substring from 0 stop-pos) from)
1957                   "'s message of " 
1958                   (if (or (not date) (string= date ""))
1959                       "(unknown date)" date)))))))
1960
1961 (defun message-make-distribution ()
1962   "Make a Distribution header."
1963   (let ((orig-distribution (message-fetch-reply-field "distribution")))
1964     (cond ((message-functionp message-distribution-function)
1965            (funcall message-distribution-function))
1966           (t orig-distribution))))
1967
1968 (defun message-make-expires ()
1969   "Return an Expires header based on `message-expires'."
1970   (let ((current (current-time))
1971         (future (* 1.0 message-expires 60 60 24)))
1972     ;; Add the future to current.
1973     (setcar current (+ (car current) (round (/ future (expt 2 16)))))
1974     (setcar (cdr current) (+ (nth 1 current) (% (round future) (expt 2 16))))
1975     ;; Return the date in the future in UT.
1976     (timezone-make-date-arpa-standard 
1977      (current-time-string current) (current-time-zone current) '(0 "UT"))))
1978
1979 (defun message-make-path ()
1980   "Return uucp path."
1981   (let ((login-name (user-login-name)))
1982     (cond ((null message-user-path)
1983            (concat (system-name) "!" login-name))
1984           ((stringp message-user-path)
1985            ;; Support GENERICPATH.  Suggested by vixie@decwrl.dec.com.
1986            (concat message-user-path "!" login-name))
1987           (t login-name))))
1988
1989 (defun message-make-from ()
1990   "Make a From header."
1991   (let* ((login (message-make-address))
1992          (fullname 
1993           (or (and (boundp 'user-full-name)
1994                    user-full-name)
1995               (user-full-name))))
1996     (when (string= fullname "&")
1997       (setq fullname (user-login-name)))
1998     (save-excursion
1999       (message-set-work-buffer)
2000       (cond 
2001        ((or (null message-from-style)
2002             (equal fullname ""))
2003         (insert login))
2004        ((or (eq message-from-style 'angles)
2005             (and (not (eq message-from-style 'parens))
2006                  ;; Use angles if no quoting is needed, or if parens would
2007                  ;; need quoting too.
2008                  (or (not (string-match "[^- !#-'*+/-9=?A-Z^-~]" fullname))
2009                      (let ((tmp (concat fullname nil)))
2010                        (while (string-match "([^()]*)" tmp)
2011                          (aset tmp (match-beginning 0) ?-)
2012                          (aset tmp (1- (match-end 0)) ?-))
2013                        (string-match "[\\()]" tmp)))))
2014         (insert fullname)
2015         (goto-char (point-min))
2016         ;; Look for a character that cannot appear unquoted
2017         ;; according to RFC 822.
2018         (when (re-search-forward "[^- !#-'*+/-9=?A-Z^-~]" nil 1)
2019           ;; Quote fullname, escaping specials.
2020           (goto-char (point-min))
2021           (insert "\"")
2022           (while (re-search-forward "[\"\\]" nil 1)
2023             (replace-match "\\\\\\&" t))
2024           (insert "\""))
2025         (insert " <" login ">"))
2026        (t                               ; 'parens or default
2027         (insert login " (")
2028         (let ((fullname-start (point)))
2029           (insert fullname)
2030           (goto-char fullname-start)
2031           ;; RFC 822 says \ and nonmatching parentheses
2032           ;; must be escaped in comments.
2033           ;; Escape every instance of ()\ ...
2034           (while (re-search-forward "[()\\]" nil 1)
2035             (replace-match "\\\\\\&" t))
2036           ;; ... then undo escaping of matching parentheses,
2037           ;; including matching nested parentheses.
2038           (goto-char fullname-start)
2039           (while (re-search-forward 
2040                   "\\(\\=\\|[^\\]\\(\\\\\\\\\\)*\\)\\\\(\\(\\([^\\]\\|\\\\\\\\\\)*\\)\\\\)"
2041                     nil 1)
2042             (replace-match "\\1(\\3)" t)
2043             (goto-char fullname-start)))
2044         (insert ")")))
2045       (buffer-string))))
2046
2047 (defun message-make-sender ()
2048   "Return the \"real\" user address.
2049 This function tries to ignore all user modifications, and 
2050 give as trustworthy answer as possible."
2051   (concat (user-login-name) "@" (system-name)))
2052
2053 (defun message-make-address ()
2054   "Make the address of the user."
2055   (or (message-user-mail-address)
2056       (concat (user-login-name) "@" (message-make-domain))))
2057
2058 (defun message-user-mail-address ()
2059   "Return the pertinent part of `user-mail-address'."
2060   (when user-mail-address
2061     (nth 1 (mail-extract-address-components user-mail-address))))
2062
2063 (defun message-make-fqdn ()
2064   "Return user's fully qualified domain name."
2065   (let ((system-name (system-name))
2066         (user-mail (message-user-mail-address)))
2067     (cond 
2068      ((string-match "[^.]\\.[^.]" system-name)
2069       ;; `system-name' returned the right result.
2070       system-name)
2071      ;; Try `mail-host-address'.
2072      ((and (boundp 'mail-host-address)
2073            (stringp mail-host-address)
2074            (string-match "\\." mail-host-address))
2075       mail-host-address)
2076      ;; We try `user-mail-address' as a backup.
2077      ((and (string-match "\\." user-mail)
2078            (string-match "@\\(.*\\)\\'" user-mail))
2079       (match-string 1 user-mail))
2080      ;; Default to this bogus thing.
2081      (t
2082       (concat system-name ".i-have-a-misconfigured-system-so-shoot-me")))))
2083
2084 (defun message-make-host-name ()
2085   "Return the name of the host."
2086   (let ((fqdn (message-make-fqdn)))
2087     (string-match "^[^.]+\\." fqdn)
2088     (substring fqdn 0 (1- (match-end 0)))))
2089
2090 (defun message-make-domain ()
2091   "Return the domain name."
2092   (or mail-host-address
2093       (message-make-fqdn)))
2094
2095 (defun message-generate-headers (headers)
2096   "Prepare article HEADERS.
2097 Headers already prepared in the buffer are not modified."
2098   (save-restriction
2099     (message-narrow-to-headers)
2100     (let* ((Date (message-make-date))
2101            (Message-ID (message-make-message-id))
2102            (Organization (message-make-organization))
2103            (From (message-make-from))
2104            (Path (message-make-path))
2105            (Subject nil)
2106            (Newsgroups nil)
2107            (In-Reply-To (message-make-in-reply-to))
2108            (To nil)
2109            (Distribution (message-make-distribution))
2110            (Lines (message-make-lines))
2111            (X-Newsreader message-newsreader)
2112            (X-Mailer (and (not (message-fetch-field "X-Newsreader"))
2113                           message-mailer))
2114            (Expires (message-make-expires))
2115            (case-fold-search t)
2116            header value elem)
2117       ;; First we remove any old generated headers.
2118       (let ((headers message-deletable-headers))
2119         (while headers
2120           (goto-char (point-min))
2121           (and (re-search-forward 
2122                 (concat "^" (symbol-name (car headers)) ": *") nil t)
2123                (get-text-property (1+ (match-beginning 0)) 'message-deletable)
2124                (message-delete-line))
2125           (pop headers)))
2126       ;; Go through all the required headers and see if they are in the
2127       ;; articles already. If they are not, or are empty, they are
2128       ;; inserted automatically - except for Subject, Newsgroups and
2129       ;; Distribution. 
2130       (while headers
2131         (goto-char (point-min))
2132         (setq elem (pop headers))
2133         (if (consp elem)
2134             (if (eq (car elem) 'optional)
2135                 (setq header (cdr elem))
2136               (setq header (car elem)))
2137           (setq header elem))
2138         (when (or (not (re-search-forward 
2139                         (concat "^" (downcase (symbol-name header)) ":") 
2140                         nil t))
2141                   (progn
2142                     ;; The header was found. We insert a space after the
2143                     ;; colon, if there is none.
2144                     (if (/= (following-char) ? ) (insert " ") (forward-char 1))
2145                     ;; Find out whether the header is empty...
2146                     (looking-at "[ \t]*$")))
2147           ;; So we find out what value we should insert.
2148           (setq value
2149                 (cond 
2150                  ((and (consp elem) (eq (car elem) 'optional))
2151                   ;; This is an optional header.  If the cdr of this
2152                   ;; is something that is nil, then we do not insert
2153                   ;; this header.
2154                   (setq header (cdr elem))
2155                   (or (and (fboundp (cdr elem)) (funcall (cdr elem)))
2156                       (and (boundp (cdr elem)) (symbol-value (cdr elem)))))
2157                  ((consp elem)
2158                   ;; The element is a cons.  Either the cdr is a
2159                   ;; string to be inserted verbatim, or it is a
2160                   ;; function, and we insert the value returned from
2161                   ;; this function.
2162                   (or (and (stringp (cdr elem)) (cdr elem))
2163                       (and (fboundp (cdr elem)) (funcall (cdr elem)))))
2164                  ((and (boundp header) (symbol-value header))
2165                   ;; The element is a symbol.  We insert the value
2166                   ;; of this symbol, if any.
2167                   (symbol-value header))
2168                  (t
2169                   ;; We couldn't generate a value for this header,
2170                   ;; so we just ask the user.
2171                   (read-from-minibuffer
2172                    (format "Empty header for %s; enter value: " header)))))
2173           ;; Finally insert the header.
2174           (when (and value 
2175                      (not (equal value "")))
2176             (save-excursion
2177               (if (bolp)
2178                   (progn
2179                     ;; This header didn't exist, so we insert it.
2180                     (goto-char (point-max))
2181                     (insert (symbol-name header) ": " value "\n")
2182                     (forward-line -1))
2183                 ;; The value of this header was empty, so we clear
2184                 ;; totally and insert the new value.
2185                 (delete-region (point) (message-point-at-eol))
2186                 (insert value))
2187               ;; Add the deletable property to the headers that require it.
2188               (and (memq header message-deletable-headers)
2189                    (progn (beginning-of-line) (looking-at "[^:]+: "))
2190                    (add-text-properties 
2191                     (point) (match-end 0)
2192                     '(message-deletable t face italic) (current-buffer)))))))
2193       ;; Insert new Sender if the From is strange. 
2194       (let ((from (message-fetch-field "from"))
2195             (sender (message-fetch-field "sender"))
2196             (secure-sender (message-make-sender)))
2197         (when (and from 
2198                    (not (message-check-element 'sender))
2199                    (not (string=
2200                          (downcase
2201                           (cadr (mail-extract-address-components from)))
2202                          (downcase secure-sender)))
2203                    (or (null sender)
2204                        (not 
2205                         (string=
2206                          (downcase
2207                           (cadr (mail-extract-address-components sender)))
2208                          (downcase secure-sender)))))
2209           (goto-char (point-min))    
2210           ;; Rename any old Sender headers to Original-Sender.
2211           (when (re-search-forward "^Sender:" nil t)
2212             (beginning-of-line)
2213             (insert "Original-")
2214             (beginning-of-line))
2215           (insert "Sender: " secure-sender "\n"))))))
2216
2217 (defun message-insert-courtesy-copy ()
2218   "Insert a courtesy message in mail copies of combined messages."
2219   (save-excursion
2220     (save-restriction
2221       (message-narrow-to-headers)
2222       (let ((newsgroups (message-fetch-field "newsgroups")))
2223         (when newsgroups
2224           (goto-char (point-max))
2225           (insert "Posted-To: " newsgroups "\n"))))
2226     (forward-line 1)
2227     (insert message-courtesy-message)))
2228     
2229 ;;;
2230 ;;; Setting up a message buffer
2231 ;;;
2232
2233 (defun message-fill-address (header value)
2234   (save-restriction
2235     (narrow-to-region (point) (point))
2236     (insert (capitalize (symbol-name header))
2237             ": "
2238             (if (consp value) (car value) value)
2239             "\n")
2240     (narrow-to-region (point-min) (1- (point-max)))
2241     (let (quoted last)
2242       (goto-char (point-min))
2243       (while (not (eobp))
2244         (skip-chars-forward "^,\"" (point-max))
2245         (if (or (= (following-char) ?,)
2246                 (eobp))
2247             (when (not quoted)
2248               (if (and (> (current-column) 78)
2249                        last)
2250                   (progn
2251                     (save-excursion
2252                       (goto-char last)
2253                       (insert "\n\t"))
2254                     (setq last (1+ (point))))
2255                 (setq last (1+ (point)))))
2256           (setq quoted (not quoted)))
2257         (unless (eobp)
2258           (forward-char 1))))
2259     (goto-char (point-max))
2260     (widen)
2261     (forward-line 1)))
2262
2263 (defun message-fill-header (header value)
2264   (let ((begin (point))
2265         (fill-column 78)
2266         (fill-prefix "\t"))
2267     (insert (capitalize (symbol-name header))
2268             ": "
2269             (if (consp value) (car value) value)
2270             "\n")
2271     (save-restriction
2272       (narrow-to-region begin (point))
2273       (fill-region-as-paragraph begin (point))
2274       ;; Tapdance around looong Message-IDs.
2275       (forward-line -1)
2276       (when (looking-at "[ \t]*$")
2277         (message-delete-line))
2278       (goto-char begin)
2279       (re-search-forward ":" nil t)
2280       (when (looking-at "\n[ \t]+")
2281         (replace-match " " t t))
2282       (goto-char (point-max)))))
2283
2284 (defun message-position-point ()
2285   "Move point to where the user probably wants to find it."
2286   (message-narrow-to-headers)
2287   (cond 
2288    ((re-search-forward "^[^:]+:[ \t]*$" nil t)
2289     (search-backward ":" )
2290     (widen)
2291     (forward-char 1)
2292     (if (= (following-char) ? )
2293         (forward-char 1)
2294       (insert " ")))
2295    (t
2296     (goto-char (point-max))
2297     (widen)
2298     (forward-line 1)
2299     (unless (looking-at "$")
2300       (forward-line 2)))
2301    (sit-for 0)))
2302
2303 (defun message-buffer-name (type &optional to group)
2304   "Return a new (unique) buffer name based on TYPE and TO."
2305   (cond
2306    ;; Check whether `message-generate-new-buffers' is a function, 
2307    ;; and if so, call it.
2308    ((message-functionp message-generate-new-buffers)
2309     (funcall message-generate-new-buffers type to group))
2310    ;; Generate a new buffer name The Message Way.
2311    (message-generate-new-buffers
2312     (generate-new-buffer-name
2313      (concat "*" type
2314              (if to
2315                  (concat " to "
2316                          (or (car (mail-extract-address-components to))
2317                              to) "")
2318                "")
2319              (if (and group (not (string= group ""))) (concat " on " group) "")
2320              "*")))
2321    ;; Use standard name.
2322    (t
2323     (format "*%s message*" type))))
2324
2325 (defun message-pop-to-buffer (name)
2326   "Pop to buffer NAME, and warn if it already exists and is modified."
2327   (let ((buffer (get-buffer name)))
2328     (if (and buffer
2329              (buffer-name buffer))
2330         (progn
2331           (set-buffer (pop-to-buffer buffer))
2332           (when (and (buffer-modified-p)
2333                      (not (y-or-n-p
2334                            "Message already being composed; erase? ")))
2335             (error "Message being composed")))
2336       (set-buffer (pop-to-buffer name))))
2337   (erase-buffer)
2338   (message-mode))
2339
2340 (defun message-do-send-housekeeping ()
2341   "Kill old message buffers."
2342   ;; We might have sent this buffer already.  Delete it from the
2343   ;; list of buffers.
2344   (setq message-buffer-list (delq (current-buffer) message-buffer-list))
2345   (while (and message-max-buffers
2346               (>= (length message-buffer-list) message-max-buffers))
2347     ;; Kill the oldest buffer -- unless it has been changed.
2348     (let ((buffer (pop message-buffer-list)))
2349       (when (and (buffer-name buffer)
2350                  (not (buffer-modified-p buffer)))
2351         (kill-buffer buffer))))
2352   ;; Rename the buffer.
2353   (if message-send-rename-function
2354       (funcall message-send-rename-function)
2355     (when (string-match "\\`\\*" (buffer-name))
2356       (rename-buffer 
2357        (concat "*sent " (substring (buffer-name) (match-end 0))) t)))
2358   ;; Push the current buffer onto the list.
2359   (when message-max-buffers
2360     (setq message-buffer-list 
2361           (nconc message-buffer-list (list (current-buffer))))))
2362
2363 (defvar mc-modes-alist)
2364 (defun message-setup (headers &optional replybuffer actions)
2365   (when (and (boundp 'mc-modes-alist)
2366              (not (assq 'message-mode mc-modes-alist)))
2367     (push '(message-mode (encrypt . mc-encrypt-message)
2368                          (sign . mc-sign-message))
2369           mc-modes-alist))
2370   (when actions
2371     (setq message-send-actions actions))
2372   (setq message-reply-buffer replybuffer)
2373   (goto-char (point-min))
2374   ;; Insert all the headers.
2375   (mail-header-format 
2376    (let ((h headers)
2377          (alist message-header-format-alist))
2378      (while h
2379        (unless (assq (caar h) message-header-format-alist)
2380          (push (list (caar h)) alist))
2381        (pop h))
2382      alist)
2383    headers)
2384   (delete-region (point) (progn (forward-line -1) (point)))
2385   (when message-default-headers
2386     (insert message-default-headers))
2387   (put-text-property
2388    (point)
2389    (progn
2390      (insert mail-header-separator "\n")
2391      (1- (point)))
2392    'read-only nil)
2393   (forward-line -1)
2394   (when (message-news-p)
2395     (when message-default-news-headers
2396       (insert message-default-news-headers))
2397     (when message-generate-headers-first
2398       (message-generate-headers
2399        (delq 'Lines
2400              (delq 'Subject
2401                    (copy-sequence message-required-news-headers))))))
2402   (when (message-mail-p)
2403     (when message-default-mail-headers
2404       (insert message-default-mail-headers))
2405     (when message-generate-headers-first
2406       (message-generate-headers
2407        (delq 'Lines
2408              (delq 'Subject
2409                    (copy-sequence message-required-mail-headers))))))
2410   (run-hooks 'message-signature-setup-hook)
2411   (message-insert-signature)
2412   (message-set-auto-save-file-name)
2413   (save-restriction
2414     (message-narrow-to-headers)
2415     (run-hooks 'message-header-setup-hook))
2416   (set-buffer-modified-p nil)
2417   (run-hooks 'message-setup-hook)
2418   (message-position-point)
2419   (undo-boundary))
2420
2421 (defun message-set-auto-save-file-name ()
2422   "Associate the message buffer with a file in the drafts directory."
2423   (when message-autosave-directory
2424     (unless (file-exists-p message-autosave-directory)
2425       (make-directory message-autosave-directory t))
2426     (let ((name (make-temp-name
2427                  (concat (file-name-as-directory message-autosave-directory)
2428                          "msg."))))
2429       (setq buffer-auto-save-file-name
2430             (save-excursion
2431               (prog1
2432                   (progn
2433                     (set-buffer (get-buffer-create " *draft tmp*"))
2434                     (setq buffer-file-name name)
2435                     (make-auto-save-file-name))
2436                 (kill-buffer (current-buffer)))))
2437       (clear-visited-file-modtime))))
2438
2439 \f
2440
2441 ;;;
2442 ;;; Commands for interfacing with message
2443 ;;;
2444
2445 ;;;###autoload
2446 (defun message-mail (&optional to subject)
2447   "Start editing a mail message to be sent."
2448   (interactive)
2449   (message-pop-to-buffer (message-buffer-name "mail" to))
2450   (message-setup `((To . ,(or to "")) (Subject . ,(or subject "")))))
2451
2452 ;;;###autoload
2453 (defun message-news (&optional newsgroups subject)
2454   "Start editing a news article to be sent."
2455   (interactive)
2456   (message-pop-to-buffer (message-buffer-name "news" nil newsgroups))
2457   (message-setup `((Newsgroups . ,(or newsgroups "")) 
2458                    (Subject . ,(or subject "")))))
2459
2460 ;;;###autoload
2461 (defun message-reply (&optional to-address wide ignore-reply-to)
2462   "Start editing a reply to the article in the current buffer."
2463   (interactive)
2464   (let ((cur (current-buffer))
2465         from subject date reply-to to cc
2466         references message-id follow-to 
2467         (inhibit-point-motion-hooks t)
2468         mct never-mct gnus-warning)
2469     (save-restriction
2470       (message-narrow-to-head)
2471       ;; Allow customizations to have their say.
2472       (if (not wide)
2473           ;; This is a regular reply.
2474           (if (message-functionp message-reply-to-function)
2475               (setq follow-to (funcall message-reply-to-function)))
2476         ;; This is a followup.
2477         (if (message-functionp message-wide-reply-to-function)
2478             (save-excursion
2479               (setq follow-to
2480                     (funcall message-wide-reply-to-function)))))
2481       ;; Find all relevant headers we need.
2482       (setq from (message-fetch-field "from")
2483             date (message-fetch-field "date") 
2484             subject (or (message-fetch-field "subject") "none")
2485             to (message-fetch-field "to")
2486             cc (message-fetch-field "cc")
2487             mct (message-fetch-field "mail-copies-to")
2488             reply-to (unless ignore-reply-to (message-fetch-field "reply-to"))
2489             references (message-fetch-field "references")
2490             message-id (message-fetch-field "message-id"))
2491       ;; Remove any (buggy) Re:'s that are present and make a
2492       ;; proper one.
2493       (when (string-match "^[ \t]*[Rr][Ee]:[ \t]*" subject)
2494         (setq subject (substring subject (match-end 0))))
2495       (setq subject (concat "Re: " subject))
2496
2497       (when (and (setq gnus-warning (message-fetch-field "gnus-warning"))
2498                  (string-match "<[^>]+>" gnus-warning))
2499         (setq message-id (match-string 0 gnus-warning)))
2500             
2501       ;; Handle special values of Mail-Copies-To.
2502       (when mct
2503         (cond ((equal (downcase mct) "never")
2504                (setq never-mct t)
2505                (setq mct nil))
2506               ((equal (downcase mct) "always")
2507                (setq mct (or reply-to from)))))
2508
2509       (unless follow-to
2510         (if (or (not wide)
2511                 to-address)
2512             (setq follow-to (list (cons 'To (or to-address reply-to from))))
2513           (let (ccalist)
2514             (save-excursion
2515               (message-set-work-buffer)
2516               (unless never-mct
2517                 (insert (or reply-to from "")))
2518               (insert (if (bolp) "" ", ") (or to ""))
2519               (insert (if mct (concat (if (bolp) "" ", ") mct) ""))
2520               (insert (if cc (concat (if (bolp) "" ", ") cc) ""))
2521               ;; Remove addresses that match `rmail-dont-reply-to-names'. 
2522               (insert (prog1 (rmail-dont-reply-to (buffer-string))
2523                         (erase-buffer)))
2524               (goto-char (point-min))
2525               (setq ccalist
2526                     (mapcar
2527                      (lambda (addr)
2528                        (cons (mail-strip-quoted-names addr) addr))
2529                      (message-tokenize-header (buffer-string))))
2530               (let ((s ccalist))
2531                 (while s
2532                   (setq ccalist (delq (assoc (car (pop s)) s) ccalist)))))
2533             (setq follow-to (list (cons 'To (cdr (pop ccalist)))))
2534             (when ccalist
2535               (push (cons 'Cc
2536                           (mapconcat (lambda (addr) (cdr addr)) ccalist ", "))
2537                     follow-to)))))
2538       (widen))
2539
2540     (message-pop-to-buffer (message-buffer-name
2541                             (if wide "wide reply" "reply") from
2542                             (if wide to-address nil)))
2543
2544     (setq message-reply-headers
2545           (vector 0 subject from date message-id references 0 0 ""))
2546
2547     (message-setup
2548      `((Subject . ,subject)
2549        ,@follow-to 
2550        ,@(if (or references message-id)
2551              `((References . ,(concat (or references "") (and references " ")
2552                                       (or message-id ""))))
2553            nil))
2554      cur)))
2555
2556 ;;;###autoload
2557 (defun message-wide-reply (&optional to-address)
2558   (interactive)
2559   (message-reply to-address t))
2560
2561 ;;;###autoload
2562 (defun message-followup ()
2563   (interactive)
2564   (let ((cur (current-buffer))
2565         from subject date reply-to mct
2566         references message-id follow-to 
2567         (inhibit-point-motion-hooks t)
2568         followup-to distribution newsgroups gnus-warning)
2569     (save-restriction
2570       (narrow-to-region
2571        (goto-char (point-min))
2572        (if (search-forward "\n\n" nil t)
2573            (1- (point))
2574          (point-max)))
2575       (when (message-functionp message-followup-to-function)
2576         (setq follow-to
2577               (funcall message-followup-to-function)))
2578       (setq from (message-fetch-field "from")
2579             date (message-fetch-field "date") 
2580             subject (or (message-fetch-field "subject") "none")
2581             references (message-fetch-field "references")
2582             message-id (message-fetch-field "message-id")
2583             followup-to (message-fetch-field "followup-to")
2584             newsgroups (message-fetch-field "newsgroups")
2585             reply-to (message-fetch-field "reply-to")
2586             distribution (message-fetch-field "distribution")
2587             mct (message-fetch-field "mail-copies-to"))
2588       (when (and (setq gnus-warning (message-fetch-field "gnus-warning"))
2589                  (string-match "<[^>]+>" gnus-warning))
2590         (setq message-id (match-string 0 gnus-warning)))
2591       ;; Remove bogus distribution.
2592       (and (stringp distribution)
2593            (string-match "world" distribution)
2594            (setq distribution nil))
2595       ;; Remove any (buggy) Re:'s that are present and make a
2596       ;; proper one.
2597       (when (string-match "^[ \t]*[Rr][Ee]:[ \t]*" subject)
2598         (setq subject (substring subject (match-end 0))))
2599       (setq subject (concat "Re: " subject))
2600       (widen))
2601
2602     (message-pop-to-buffer (message-buffer-name "followup" from newsgroups))
2603
2604     (message-setup
2605      `((Subject . ,subject)
2606        ,@(cond 
2607           (follow-to follow-to)
2608           ((and followup-to message-use-followup-to)
2609            (list
2610             (cond 
2611              ((equal (downcase followup-to) "poster")
2612               (if (or (eq message-use-followup-to 'use)
2613                       (message-y-or-n-p "Obey Followup-To: poster? " t "\
2614 You should normally obey the Followup-To: header.
2615
2616 `Followup-To: poster' sends your response via e-mail instead of news.
2617
2618 A typical situation where `Followup-To: poster' is used is when the poster
2619 does not read the newsgroup, so he wouldn't see any replies sent to it."))
2620                   (cons 'To (or reply-to from ""))
2621                 (cons 'Newsgroups newsgroups)))
2622              (t
2623               (if (or (equal followup-to newsgroups)
2624                       (not (eq message-use-followup-to 'ask))
2625                       (message-y-or-n-p
2626                        (concat "Obey Followup-To: " followup-to "? ") t "\
2627 You should normally obey the Followup-To: header.
2628
2629         `Followup-To: " followup-to "'
2630 directs your response to " (if (string-match "," followup-to)
2631                                "the specified newsgroups"
2632                              "that newsgroup only") ".
2633
2634 If a message is posted to several newsgroups, Followup-To is often
2635 used to direct the following discussion to one newsgroup only,
2636 because discussions that are spread over several newsgroup tend to
2637 be fragmented and very difficult to follow.
2638
2639 Also, some source/announcment newsgroups are not indented for discussion;
2640 responses here are directed to other newsgroups."))
2641                   (cons 'Newsgroups followup-to)
2642                 (cons 'Newsgroups newsgroups))))))
2643           (t
2644            `((Newsgroups . ,newsgroups))))
2645        ,@(and distribution (list (cons 'Distribution distribution)))
2646        (References . ,(concat (or references "") (and references " ")
2647                               (or message-id "")))
2648        ,@(when (and mct
2649                     (not (equal (downcase mct) "never")))
2650            (list (cons 'Cc (if (equal (downcase mct) "always")
2651                                (or reply-to from "")
2652                              mct)))))
2653
2654      cur)
2655
2656     (setq message-reply-headers
2657           (vector 0 subject from date message-id references 0 0 ""))))
2658
2659
2660 ;;;###autoload
2661 (defun message-cancel-news ()
2662   "Cancel an article you posted."
2663   (interactive)
2664   (unless (message-news-p)
2665     (error "This is not a news article; canceling is impossible"))
2666   (when (yes-or-no-p "Do you really want to cancel this article? ")
2667     (let (from newsgroups message-id distribution buf)
2668       (save-excursion
2669         ;; Get header info. from original article.
2670         (save-restriction
2671           (message-narrow-to-head)
2672           (setq from (message-fetch-field "from")
2673                 newsgroups (message-fetch-field "newsgroups")
2674                 message-id (message-fetch-field "message-id")
2675                 distribution (message-fetch-field "distribution")))
2676         ;; Make sure that this article was written by the user.
2677         (unless (string-equal
2678                  (downcase (cadr (mail-extract-address-components from)))
2679                  (downcase (message-make-address)))
2680           (error "This article is not yours"))
2681         ;; Make control message.
2682         (setq buf (set-buffer (get-buffer-create " *message cancel*")))
2683         (buffer-disable-undo (current-buffer))
2684         (erase-buffer)
2685         (insert "Newsgroups: " newsgroups "\n"
2686                 "From: " (message-make-from) "\n"
2687                 "Subject: cmsg cancel " message-id "\n"
2688                 "Control: cancel " message-id "\n"
2689                 (if distribution
2690                     (concat "Distribution: " distribution "\n")
2691                   "")
2692                 mail-header-separator "\n"
2693                 message-cancel-message)
2694         (message "Canceling your article...")
2695         (let ((message-syntax-checks 'dont-check-for-anything-just-trust-me))
2696           (funcall message-send-news-function))
2697         (message "Canceling your article...done")
2698         (kill-buffer buf)))))
2699
2700 ;;;###autoload
2701 (defun message-supersede ()
2702   "Start composing a message to supersede the current message.
2703 This is done simply by taking the old article and adding a Supersedes
2704 header line with the old Message-ID."
2705   (interactive)
2706   (let ((cur (current-buffer)))
2707     ;; Check whether the user owns the article that is to be superseded. 
2708     (unless (string-equal
2709              (downcase (cadr (mail-extract-address-components
2710                               (message-fetch-field "from"))))
2711              (downcase (message-make-address)))
2712       (error "This article is not yours"))
2713     ;; Get a normal message buffer.
2714     (message-pop-to-buffer (message-buffer-name "supersede"))
2715     (insert-buffer-substring cur)
2716     (message-narrow-to-head)
2717     ;; Remove unwanted headers.
2718     (when message-ignored-supersedes-headers
2719       (message-remove-header message-ignored-supersedes-headers t))
2720     (goto-char (point-min))
2721     (if (not (re-search-forward "^Message-ID: " nil t))
2722         (error "No Message-ID in this article")
2723       (replace-match "Supersedes: " t t))
2724     (goto-char (point-max))
2725     (insert mail-header-separator)
2726     (widen)
2727     (forward-line 1)))
2728
2729 ;;;###autoload
2730 (defun message-recover ()
2731   "Reread contents of current buffer from its last auto-save file."
2732   (interactive)
2733   (let ((file-name (make-auto-save-file-name)))
2734     (cond ((save-window-excursion
2735              (if (not (eq system-type 'vax-vms))
2736                  (with-output-to-temp-buffer "*Directory*"
2737                    (buffer-disable-undo standard-output)
2738                    (let ((default-directory "/"))
2739                      (call-process
2740                       "ls" nil standard-output nil "-l" file-name))))
2741              (yes-or-no-p (format "Recover auto save file %s? " file-name)))
2742            (let ((buffer-read-only nil))
2743              (erase-buffer)
2744              (insert-file-contents file-name nil)))
2745           (t (error "message-recover cancelled")))))
2746
2747 ;;; Forwarding messages.
2748
2749 (defun message-make-forward-subject ()
2750   "Return a Subject header suitable for the message in the current buffer."
2751   (concat "[" (or (message-fetch-field (if (message-news-p) "newsgroups" "from"))
2752                   "(nowhere)")
2753           "] " (or (message-fetch-field "Subject") "")))
2754
2755 ;;;###autoload
2756 (defun message-forward (&optional news)
2757   "Forward the current message via mail.  
2758 Optional NEWS will use news to forward instead of mail."
2759   (interactive "P")
2760   (let ((cur (current-buffer))
2761         (subject (message-make-forward-subject))
2762         art-beg)
2763     (if news (message-news nil subject) (message-mail nil subject))
2764     ;; Put point where we want it before inserting the forwarded
2765     ;; message. 
2766     (if message-signature-before-forwarded-message
2767         (goto-char (point-max))
2768       (message-goto-body))
2769     ;; Make sure we're at the start of the line.
2770     (unless (eolp)
2771       (insert "\n"))
2772     ;; Narrow to the area we are to insert.
2773     (narrow-to-region (point) (point))
2774     ;; Insert the separators and the forwarded buffer.
2775     (insert message-forward-start-separator)
2776     (setq art-beg (point))
2777     (insert-buffer-substring cur)
2778     (goto-char (point-max))
2779     (insert message-forward-end-separator)
2780     (set-text-properties (point-min) (point-max) nil)
2781     ;; Remove all unwanted headers.
2782     (goto-char art-beg)
2783     (narrow-to-region (point) (if (search-forward "\n\n" nil t)
2784                                   (1- (point))
2785                                 (point)))
2786     (goto-char (point-min))
2787     (message-remove-header message-included-forward-headers t nil t)
2788     (widen)
2789     (message-position-point)))
2790
2791 ;;;###autoload
2792 (defun message-resend (address)
2793   "Resend the current article to ADDRESS."
2794   (interactive "sResend message to: ")
2795   (save-excursion
2796     (let ((cur (current-buffer))
2797           beg)
2798       ;; We first set up a normal mail buffer.
2799       (set-buffer (get-buffer-create " *message resend*"))
2800       (buffer-disable-undo (current-buffer))
2801       (erase-buffer)
2802       (message-setup `((To . ,address)))
2803       ;; Insert our usual headers.
2804       (message-generate-headers '(From Date To))
2805       (message-narrow-to-headers)
2806       ;; Rename them all to "Resent-*".
2807       (while (re-search-forward "^[A-Za-z]" nil t)
2808         (forward-char -1)
2809         (insert "Resent-"))
2810       (widen)
2811       (forward-line)
2812       (delete-region (point) (point-max))
2813       (setq beg (point))
2814       ;; Insert the message to be resent.
2815       (insert-buffer-substring cur)
2816       (goto-char (point-min))
2817       (search-forward "\n\n")
2818       (forward-char -1)
2819       (save-restriction
2820         (narrow-to-region beg (point))
2821         (message-remove-header message-ignored-resent-headers t)
2822         (goto-char (point-max)))
2823       (insert mail-header-separator)
2824       ;; Rename all old ("Also-")Resent headers.
2825       (while (re-search-backward "^\\(Also-\\)?Resent-" beg t)
2826         (beginning-of-line)
2827         (insert "Also-"))
2828       ;; Send it.
2829       (message-send-mail)
2830       (kill-buffer (current-buffer)))))
2831
2832 ;;;###autoload
2833 (defun message-bounce ()
2834   "Re-mail the current message.
2835 This only makes sense if the current message is a bounce message than
2836 contains some mail you have written which has been bounced back to
2837 you."
2838   (interactive)
2839   (let ((cur (current-buffer))
2840         boundary)
2841     (message-pop-to-buffer (message-buffer-name "bounce"))
2842     (insert-buffer-substring cur)
2843     (undo-boundary)
2844     (message-narrow-to-head)
2845     (if (and (message-fetch-field "Mime-Version")
2846              (setq boundary (message-fetch-field "Content-Type")))
2847         (if (string-match "boundary=\"\\([^\"]+\\)\"" boundary)
2848             (setq boundary (concat (match-string 1 boundary) " *\n"
2849                                    "Content-Type: message/rfc822"))
2850           (setq boundary nil)))
2851     (widen)
2852     (goto-char (point-min))
2853     (search-forward "\n\n" nil t)
2854     (or (and boundary
2855              (re-search-forward boundary nil t)
2856              (forward-line 2))
2857         (and (re-search-forward message-unsent-separator nil t)
2858              (forward-line 1))
2859         (and (search-forward "\n\n" nil t)
2860              (re-search-forward "^Return-Path:.*\n" nil t)))
2861     ;; We remove everything before the bounced mail.
2862     (delete-region 
2863      (point-min)
2864      (if (re-search-forward "^[^ \n\t]+:" nil t)
2865          (match-beginning 0)
2866        (point)))
2867     (save-restriction
2868       (message-narrow-to-head)
2869       (message-remove-header message-ignored-bounced-headers t)
2870       (goto-char (point-max))
2871       (insert mail-header-separator))
2872     (message-position-point)))
2873
2874 ;;;
2875 ;;; Interactive entry points for new message buffers.
2876 ;;;
2877
2878 ;;;###autoload
2879 (defun message-mail-other-window (&optional to subject)
2880   "Like `message-mail' command, but display mail buffer in another window."
2881   (interactive)
2882   (let ((pop-up-windows t)
2883         (special-display-buffer-names nil)
2884         (special-display-regexps nil)
2885         (same-window-buffer-names nil)
2886         (same-window-regexps nil))
2887     (message-pop-to-buffer (message-buffer-name "mail" to)))
2888   (message-setup `((To . ,(or to "")) (Subject . ,(or subject "")))))
2889
2890 ;;;###autoload
2891 (defun message-mail-other-frame (&optional to subject)
2892   "Like `message-mail' command, but display mail buffer in another frame."
2893   (interactive)
2894   (let ((pop-up-frames t)
2895         (special-display-buffer-names nil)
2896         (special-display-regexps nil)
2897         (same-window-buffer-names nil)
2898         (same-window-regexps nil))
2899     (message-pop-to-buffer (message-buffer-name "mail" to)))
2900   (message-setup `((To . ,(or to "")) (Subject . ,(or subject "")))))
2901
2902 ;;;###autoload
2903 (defun message-news-other-window (&optional newsgroups subject)
2904   "Start editing a news article to be sent."
2905   (interactive)
2906   (let ((pop-up-windows t)
2907         (special-display-buffer-names nil)
2908         (special-display-regexps nil)
2909         (same-window-buffer-names nil)
2910         (same-window-regexps nil))
2911     (message-pop-to-buffer (message-buffer-name "news" nil newsgroups)))
2912   (message-setup `((Newsgroups . ,(or newsgroups "")) 
2913                    (Subject . ,(or subject "")))))
2914
2915 ;;;###autoload
2916 (defun message-news-other-frame (&optional newsgroups subject)
2917   "Start editing a news article to be sent."
2918   (interactive)
2919   (let ((pop-up-frames t)
2920         (special-display-buffer-names nil)
2921         (special-display-regexps nil)
2922         (same-window-buffer-names nil)
2923         (same-window-regexps nil))
2924     (message-pop-to-buffer (message-buffer-name "news" nil newsgroups)))
2925   (message-setup `((Newsgroups . ,(or newsgroups "")) 
2926                    (Subject . ,(or subject "")))))
2927
2928 ;;; underline.el
2929
2930 ;; This code should be moved to underline.el (from which it is stolen). 
2931
2932 ;;;###autoload
2933 (defun bold-region (start end)
2934   "Bold all nonblank characters in the region.
2935 Works by overstriking characters.
2936 Called from program, takes two arguments START and END
2937 which specify the range to operate on."
2938   (interactive "r")
2939   (save-excursion
2940    (let ((end1 (make-marker)))
2941      (move-marker end1 (max start end))
2942      (goto-char (min start end))
2943      (while (< (point) end1)
2944        (or (looking-at "[_\^@- ]")
2945            (insert (following-char) "\b"))
2946        (forward-char 1)))))
2947
2948 ;;;###autoload
2949 (defun unbold-region (start end)
2950   "Remove all boldness (overstruck characters) in the region.
2951 Called from program, takes two arguments START and END
2952 which specify the range to operate on."
2953   (interactive "r")
2954   (save-excursion
2955    (let ((end1 (make-marker)))
2956      (move-marker end1 (max start end))
2957      (goto-char (min start end)) 
2958      (while (re-search-forward "\b" end1 t)
2959        (if (eq (following-char) (char-after (- (point) 2)))
2960            (delete-char -2))))))
2961
2962 (defalias 'message-exchange-point-and-mark 'exchange-point-and-mark)
2963
2964 ;; Support for toolbar
2965 (when (string-match "XEmacs\\|Lucid" emacs-version)
2966   (require 'messagexmas))
2967
2968 ;;; Group name completion.
2969
2970 (defvar message-newgroups-header-regexp
2971   "^\\(Newsgroups\\|Followup-To\\|Posted-To\\):"
2972   "Regexp that match headers that lists groups.")
2973
2974 (defun message-tab ()
2975   "Expand group names in Newsgroups and Followup-To headers.
2976 Do a `tab-to-tab-stop' if not in those headers."
2977   (interactive)
2978   (if (let ((mail-abbrev-mode-regexp message-newgroups-header-regexp))
2979         (mail-abbrev-in-expansion-header-p))
2980       (message-expand-group)
2981     (tab-to-tab-stop)))
2982
2983 (defvar gnus-active-hashtb)
2984 (defun message-expand-group ()
2985   (let* ((b (save-excursion (skip-chars-backward "^, :\t\n") (point)))
2986          (completion-ignore-case t)
2987          (string (buffer-substring b (point)))
2988          (hashtb (and (boundp 'gnus-active-hashtb) gnus-active-hashtb))
2989          (completions (all-completions string hashtb))
2990          (cur (current-buffer))
2991          comp)
2992     (delete-region b (point))
2993     (cond 
2994      ((= (length completions) 1)
2995       (if (string= (car completions) string)
2996           (progn
2997             (insert string)
2998             (message "Only matching group"))
2999         (insert (car completions))))
3000      ((and (setq comp (try-completion string hashtb))
3001            (not (string= comp string)))
3002       (insert comp))
3003      (t
3004       (insert string)
3005       (if (not comp)
3006           (message "No matching groups")
3007         (pop-to-buffer "*Completions*")
3008         (buffer-disable-undo (current-buffer))
3009         (let ((buffer-read-only nil))
3010           (erase-buffer)
3011           (let ((standard-output (current-buffer)))
3012             (display-completion-list (sort completions 'string<)))
3013           (goto-char (point-min))
3014           (pop-to-buffer cur)))))))
3015
3016 ;;; Help stuff.
3017
3018 (defun message-talkative-question (ask question show &rest text)
3019   "Call FUNCTION with argument QUESTION, displaying the rest of the arguments in a temporary buffer if SHOW.  
3020 The following arguments may contain lists of values."
3021   (if (and show
3022            (setq text (message-flatten-list text)))
3023       (save-window-excursion
3024         (save-excursion
3025           (with-output-to-temp-buffer " *MESSAGE information message*"
3026             (set-buffer " *MESSAGE information message*")
3027             (mapcar 'princ text)
3028             (goto-char (point-min))))
3029         (funcall ask question))
3030     (funcall ask question)))
3031
3032 (defun message-flatten-list (&rest list)
3033   (message-flatten-list-1 list))
3034
3035 (defun message-flatten-list-1 (list)
3036   (cond ((consp list) 
3037          (apply 'append (mapcar 'message-flatten-list-1 list)))
3038         (list
3039          (list list))))
3040
3041 (run-hooks 'message-load-hook)
3042
3043 (provide 'message)
3044
3045 ;;; message.el ends here