(rmail-nonignored-headers): New variable.
[bpt/emacs.git] / lisp / mail / rmail.el
1 ;;; rmail.el --- main code of "RMAIL" mail reader for Emacs
2
3 ;; Copyright (C) 1985, 1986, 1987, 1988, 1993, 1994, 1995, 1996, 1997, 1998,
4 ;; 2000, 2001, 2002, 2003, 2004, 2005 Free Software Foundation, Inc.
5
6 ;; Maintainer: FSF
7 ;; Keywords: mail
8
9 ;; This file is part of GNU Emacs.
10
11 ;; GNU Emacs is free software; you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation; either version 2, or (at your option)
14 ;; any later version.
15
16 ;; GNU Emacs is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 ;; GNU General Public License for more details.
20
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs; see the file COPYING. If not, write to the
23 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
24 ;; Boston, MA 02110-1301, USA.
25
26 ;;; Commentary:
27
28 ;;; Code:
29
30 ;; Souped up by shane@mit-ajax based on ideas of rlk@athena.mit.edu
31 ;; New features include attribute and keyword support, message
32 ;; selection by dispatch table, summary by attributes and keywords,
33 ;; expunging by dispatch table, sticky options for file commands.
34
35 ;; Extended by Bob Weiner of Motorola
36 ;; New features include: rmail and rmail-summary buffers remain
37 ;; synchronized and key bindings basically operate the same way in both
38 ;; buffers, summary by topic or by regular expression, rmail-reply-prefix
39 ;; variable, and a bury rmail buffer (wipe) command.
40 ;;
41
42 (require 'mail-utils)
43 (eval-when-compile (require 'mule-util)) ; for detect-coding-with-priority
44
45 ; These variables now declared in paths.el.
46 ;(defvar rmail-spool-directory "/usr/spool/mail/"
47 ; "This is the name of the directory used by the system mailer for\n\
48 ;delivering new mail. Its name should end with a slash.")
49 ;(defvar rmail-file-name
50 ; (expand-file-name "~/RMAIL")
51 ; "")
52
53 (defgroup rmail nil
54 "Mail reader for Emacs."
55 :group 'mail)
56
57 (defgroup rmail-retrieve nil
58 "Rmail retrieval options."
59 :prefix "rmail-"
60 :group 'rmail)
61
62 (defgroup rmail-files nil
63 "Rmail files."
64 :prefix "rmail-"
65 :group 'rmail)
66
67 (defgroup rmail-headers nil
68 "Rmail header options."
69 :prefix "rmail-"
70 :group 'rmail)
71
72 (defgroup rmail-reply nil
73 "Rmail reply options."
74 :prefix "rmail-"
75 :group 'rmail)
76
77 (defgroup rmail-summary nil
78 "Rmail summary options."
79 :prefix "rmail-"
80 :prefix "rmail-summary-"
81 :group 'rmail)
82
83 (defgroup rmail-output nil
84 "Output message to a file."
85 :prefix "rmail-output-"
86 :prefix "rmail-"
87 :group 'rmail)
88
89 (defgroup rmail-edit nil
90 "Rmail editing."
91 :prefix "rmail-edit-"
92 :group 'rmail)
93
94 (defgroup rmail-obsolete nil
95 "Rmail obsolete customization variables."
96 :group 'rmail)
97
98 (defcustom rmail-movemail-program nil
99 "If non-nil, the file name of the `movemail' program."
100 :group 'rmail-retrieve
101 :type '(choice (const nil) string))
102
103 (defcustom rmail-pop-password nil
104 "*Password to use when reading mail from POP server.
105 Please use `rmail-remote-password' instead."
106 :type '(choice (string :tag "Password")
107 (const :tag "Not Required" nil))
108 :group 'rmail-obsolete)
109
110 (defcustom rmail-pop-password-required nil
111 "*Non-nil if a password is required when reading mail from a POP server.
112 Please use rmail-remote-password-required instead."
113 :type 'boolean
114 :group 'rmail-obsolete)
115
116 (defcustom rmail-remote-password nil
117 "*Password to use when reading mail from a remote server.
118 This setting is ignored for mailboxes whose URL already contains a password."
119 :type '(choice (string :tag "Password")
120 (const :tag "Not Required" nil))
121 :set-after '(rmail-pop-password)
122 :set #'(lambda (symbol value)
123 (set-default symbol
124 (if (and (not value)
125 (boundp 'rmail-pop-password)
126 rmail-pop-password)
127 rmail-pop-password
128 value))
129 (setq rmail-pop-password nil))
130 :group 'rmail-retrieve
131 :version "22.1")
132
133 (defcustom rmail-remote-password-required nil
134 "*Non-nil if a password is required when reading mail from a remote server."
135 :type 'boolean
136 :set-after '(rmail-pop-password-required)
137 :set #'(lambda (symbol value)
138 (set-default symbol
139 (if (and (not value)
140 (boundp 'rmail-pop-password-required)
141 rmail-pop-password-required)
142 rmail-pop-password-required
143 value))
144 (setq rmail-pop-password-required nil))
145 :group 'rmail-retrieve
146 :version "22.1")
147
148 (defcustom rmail-movemail-flags nil
149 "*List of flags to pass to movemail.
150 Most commonly used to specify `-g' to enable GSS-API authentication
151 or `-k' to enable Kerberos authentication."
152 :type '(repeat string)
153 :group 'rmail-retrieve
154 :version "20.3")
155
156 (defvar rmail-remote-password-error "invalid usercode or password\\|
157 unknown user name or bad password\\|Authentication failed\\|MU_ERR_AUTH_FAILURE"
158 "Regular expression matching incorrect-password POP or IMAP server error
159 messages.
160 If you get an incorrect-password error that this expression does not match,
161 please report it with \\[report-emacs-bug].")
162
163 (defvar rmail-encoded-remote-password nil)
164
165 (defcustom rmail-preserve-inbox nil
166 "*Non-nil means leave incoming mail in the user's inbox--don't delete it."
167 :type 'boolean
168 :group 'rmail-retrieve)
169
170 (defcustom rmail-movemail-search-path nil
171 "*List of directories to search for movemail (in addition to `exec-path')."
172 :group 'rmail-retrieve
173 :type '(repeat (directory)))
174
175 (defun rmail-probe (prog)
176 "Determine what flavor of movemail PROG is.
177 We do this by executing it with `--version' and analyzing its output."
178 (with-temp-buffer
179 (let ((tbuf (current-buffer)))
180 (buffer-disable-undo tbuf)
181 (call-process prog nil tbuf nil "--version")
182 (if (not (buffer-modified-p tbuf))
183 ;; Should not happen...
184 nil
185 (goto-char (point-min))
186 (cond
187 ((looking-at ".*movemail: invalid option")
188 'emacs) ;; Possibly...
189 ((looking-at "movemail (GNU Mailutils .*)")
190 'mailutils)
191 (t
192 ;; FIXME:
193 'emacs))))))
194
195 (defun rmail-autodetect ()
196 "Determine and return the file name of the `movemail' program.
197 If `rmail-movemail-program' is non-nil, use it.
198 Otherwise, look for `movemail' in the directories in
199 `rmail-movemail-search-path', those in `exec-path', and `exec-directory'."
200 (if rmail-movemail-program
201 (rmail-probe rmail-movemail-program)
202 (catch 'scan
203 (dolist (dir (append rmail-movemail-search-path exec-path
204 (list exec-directory)))
205 (when (and dir (file-accessible-directory-p dir))
206 (let ((progname (expand-file-name "movemail" dir)))
207 (when (and (not (file-directory-p progname))
208 (file-executable-p progname))
209 (let ((x (rmail-probe progname)))
210 (when x
211 (setq rmail-movemail-program progname)
212 (throw 'scan x))))))))))
213
214 (defvar rmail-movemail-variant-in-use nil
215 "The movemail variant currently in use. Known variants are:
216
217 `emacs' Means any implementation, compatible with the native Emacs one.
218 This is the default;
219 `mailutils' Means GNU mailutils implementation, capable of handling full
220 mail URLs as the source mailbox;")
221
222 ;;;###autoload
223 (defun rmail-movemail-variant-p (&rest variants)
224 "Return t if the current movemail variant is any of VARIANTS.
225 Currently known variants are 'emacs and 'mailutils."
226 (when (not rmail-movemail-variant-in-use)
227 ;; Autodetect
228 (setq rmail-movemail-variant-in-use (rmail-autodetect)))
229 (not (null (member rmail-movemail-variant-in-use variants))))
230
231 ;;;###autoload
232 (defcustom rmail-dont-reply-to-names nil "\
233 *A regexp specifying addresses to prune from a reply message.
234 A value of nil means exclude your own email address as an address
235 plus whatever is specified by `rmail-default-dont-reply-to-names'."
236 :type '(choice regexp (const :tag "Your Name" nil))
237 :group 'rmail-reply)
238
239 ;;;###autoload
240 (defvar rmail-default-dont-reply-to-names "\\`info-" "\
241 A regular expression specifying part of the default value of the
242 variable `rmail-dont-reply-to-names', for when the user does not set
243 `rmail-dont-reply-to-names' explicitly. (The other part of the default
244 value is the user's email address and name.)
245 It is useful to set this variable in the site customization file.")
246
247 ;;;###autoload
248 (defcustom rmail-ignored-headers
249 (concat "^via:\\|^mail-from:\\|^origin:\\|^references:\\|^sender:"
250 "\\|^status:\\|^received:\\|^x400-originator:\\|^x400-recipients:"
251 "\\|^x400-received:\\|^x400-mts-identifier:\\|^x400-content-type:"
252 "\\|^\\(resent-\\|\\)message-id:\\|^summary-line:\\|^resent-date:"
253 "\\|^nntp-posting-host:\\|^path:\\|^x-char.*:\\|^x-face:\\|^face:"
254 "\\|^x-mailer:\\|^delivered-to:\\|^lines:"
255 "\\|^content-transfer-encoding:\\|^x-coding-system:"
256 "\\|^return-path:\\|^errors-to:\\|^return-receipt-to:"
257 "\\|^precedence:\\|^list-help:\\|^list-post:\\|^list-subscribe:"
258 "\\|^list-id:\\|^list-unsubscribe:\\|^list-archive:"
259 "\\|^content-length:\\|^nntp-posting-date:\\|^user-agent"
260 "\\|^importance:\\|^envelope-to:\\|^delivery-date\\|^openpgp:"
261 "\\|^mbox-line:\\|^cancel-lock:"
262 "\\|^resent-face:\\|^resent-x.*:\\|^resent-organization:\\|^resent-openpgp:"
263
264 "\\|^x-.*:")
265 "*Regexp to match header fields that Rmail should normally hide.
266 \(See also `rmail-nonignored-headers', which overrides this regexp.)
267 This variable is used for reformatting the message header,
268 which normally happens once for each message,
269 when you view the message for the first time in Rmail.
270 To make a change in this variable take effect
271 for a message that you have already viewed,
272 go to that message and type \\[rmail-toggle-header] twice."
273 :type 'regexp
274 :group 'rmail-headers)
275
276 (defcustom rmail-nonignored-headers "^x-spam-status:"
277 "*Regexp to match X header fields that Rmail should show.
278 This regexp overrides `rmail-ignored-headers'; if both this regexp
279 and that one match a certain header field, Rmail shows the field.
280
281 This variable is used for reformatting the message header,
282 which normally happens once for each message,
283 when you view the message for the first time in Rmail.
284 To make a change in this variable take effect
285 for a message that you have already viewed,
286 go to that message and type \\[rmail-toggle-header] twice."
287 :type 'regexp
288 :group 'rmail-headers)
289
290 ;;;###autoload
291 (defcustom rmail-displayed-headers nil
292 "*Regexp to match Header fields that Rmail should display.
293 If nil, display all header fields except those matched by
294 `rmail-ignored-headers'."
295 :type '(choice regexp (const :tag "All"))
296 :group 'rmail-headers)
297
298 ;;;###autoload
299 (defcustom rmail-retry-ignored-headers "^x-authentication-warning:" "\
300 *Headers that should be stripped when retrying a failed message."
301 :type '(choice regexp (const nil :tag "None"))
302 :group 'rmail-headers)
303
304 ;;;###autoload
305 (defcustom rmail-highlighted-headers "^From:\\|^Subject:" "\
306 *Regexp to match Header fields that Rmail should normally highlight.
307 A value of nil means don't highlight.
308 See also `rmail-highlight-face'."
309 :type 'regexp
310 :group 'rmail-headers)
311
312 ;;;###autoload
313 (defcustom rmail-highlight-face nil "\
314 *Face used by Rmail for highlighting headers."
315 :type '(choice (const :tag "Default" nil)
316 face)
317 :group 'rmail-headers)
318
319 ;;;###autoload
320 (defcustom rmail-delete-after-output nil "\
321 *Non-nil means automatically delete a message that is copied to a file."
322 :type 'boolean
323 :group 'rmail-files)
324
325 ;;;###autoload
326 (defcustom rmail-primary-inbox-list nil "\
327 *List of files which are inboxes for user's primary mail file `~/RMAIL'.
328 nil means the default, which is (\"/usr/spool/mail/$USER\")
329 \(the name varies depending on the operating system,
330 and the value of the environment variable MAIL overrides it)."
331 ;; Don't use backquote here, because we don't want to need it
332 ;; at load time.
333 :type (list 'choice '(const :tag "Default" nil)
334 (list 'repeat ':value (list (or (getenv "MAIL")
335 (concat "/var/spool/mail/"
336 (getenv "USER"))))
337 'file))
338 :group 'rmail-retrieve
339 :group 'rmail-files)
340
341 ;;;###autoload
342 (defcustom rmail-mail-new-frame nil
343 "*Non-nil means Rmail makes a new frame for composing outgoing mail.
344 This is handy if you want to preserve the window configuration of
345 the frame where you have the RMAIL buffer displayed."
346 :type 'boolean
347 :group 'rmail-reply)
348
349 ;;;###autoload
350 (defcustom rmail-secondary-file-directory "~/"
351 "*Directory for additional secondary Rmail files."
352 :type 'directory
353 :group 'rmail-files)
354 ;;;###autoload
355 (defcustom rmail-secondary-file-regexp "\\.xmail$"
356 "*Regexp for which files are secondary Rmail files."
357 :type 'regexp
358 :group 'rmail-files)
359
360 ;;;###autoload
361 (defcustom rmail-confirm-expunge 'y-or-n-p
362 "*Whether and how to ask for confirmation before expunging deleted messages."
363 :type '(choice (const :tag "No confirmation" nil)
364 (const :tag "Confirm with y-or-n-p" y-or-n-p)
365 (const :tag "Confirm with yes-or-no-p" yes-or-no-p))
366 :version "21.1"
367 :group 'rmail-files)
368
369 ;;;###autoload
370 (defvar rmail-mode-hook nil
371 "List of functions to call when Rmail is invoked.")
372
373 ;;;###autoload
374 (defvar rmail-get-new-mail-hook nil
375 "List of functions to call when Rmail has retrieved new mail.")
376
377 ;;;###autoload
378 (defcustom rmail-show-message-hook nil
379 "List of functions to call when Rmail displays a message."
380 :type 'hook
381 :options '(goto-address)
382 :group 'rmail)
383
384 ;;;###autoload
385 (defvar rmail-quit-hook nil
386 "List of functions to call when quitting out of Rmail.")
387
388 ;;;###autoload
389 (defvar rmail-delete-message-hook nil
390 "List of functions to call when Rmail deletes a message.
391 When the hooks are called, the message has been marked deleted but is
392 still the current message in the Rmail buffer.")
393
394 ;; These may be altered by site-init.el to match the format of mmdf files
395 ;; delimiting used on a given host (delim1 and delim2 from the config
396 ;; files).
397
398 (defvar rmail-mmdf-delim1 "^\001\001\001\001\n"
399 "Regexp marking the start of an mmdf message.")
400 (defvar rmail-mmdf-delim2 "^\001\001\001\001\n"
401 "Regexp marking the end of an mmdf message.")
402
403 (defcustom rmail-message-filter nil
404 "If non-nil, a filter function for new messages in RMAIL.
405 Called with region narrowed to the message, including headers,
406 before obeying `rmail-ignored-headers'."
407 :group 'rmail-headers
408 :type 'function)
409
410 (defcustom rmail-automatic-folder-directives nil
411 "List of directives specifying where to put a message.
412 Each element of the list is of the form:
413
414 (FOLDERNAME FIELD REGEXP [ FIELD REGEXP ] ... )
415
416 Where FOLDERNAME is the name of a BABYL format folder to put the
417 message. If any of the field regexp's are nil, then it is ignored.
418
419 If FOLDERNAME is \"/dev/null\", it is deleted.
420 If FOLDERNAME is nil then it is deleted, and skipped.
421
422 FIELD is the plain text name of a field in the message, such as
423 \"subject\" or \"from\". A FIELD of \"to\" will automatically include
424 all text from the \"cc\" field as well.
425
426 REGEXP is an expression to match in the preceeding specified FIELD.
427 FIELD/REGEXP pairs continue in the list.
428
429 examples:
430 (\"/dev/null\" \"from\" \"@spam.com\") ; delete all mail from spam.com
431 (\"RMS\" \"from\" \"rms@\") ; save all mail from RMS."
432 :group 'rmail
433 :version "21.1"
434 :type '(repeat (sexp :tag "Directive")))
435
436 (defvar rmail-reply-prefix "Re: "
437 "String to prepend to Subject line when replying to a message.")
438
439 ;; Some mailers use "Re(2):" or "Re^2:" or "Re: Re:" or "Re[2]:".
440 ;; This pattern should catch all the common variants.
441 (defvar rmail-reply-regexp "\\`\\(Re\\(([0-9]+)\\|\\[[0-9]+\\]\\|\\^[0-9]+\\)?: *\\)*"
442 "Regexp to delete from Subject line before inserting `rmail-reply-prefix'.")
443
444 (defcustom rmail-display-summary nil
445 "*If non-nil, Rmail always displays the summary buffer."
446 :group 'rmail-summary
447 :type 'boolean)
448 \f
449 (defvar rmail-inbox-list nil)
450 (put 'rmail-inbox-list 'permanent-local t)
451
452 (defvar rmail-keywords nil)
453 (put 'rmail-keywords 'permanent-local t)
454
455 (defvar rmail-buffer nil
456 "The RMAIL buffer related to the current buffer.
457 In an RMAIL buffer, this holds the RMAIL buffer itself.
458 In a summary buffer, this holds the RMAIL buffer it is a summary for.")
459 (put 'rmail-buffer 'permanent-local t)
460
461 ;; Message counters and markers. Deleted flags.
462
463 (defvar rmail-current-message nil)
464 (put 'rmail-current-message 'permanent-local t)
465
466 (defvar rmail-total-messages nil)
467 (put 'rmail-total-messages 'permanent-local t)
468
469 (defvar rmail-message-vector nil)
470 (put 'rmail-message-vector 'permanent-local t)
471
472 (defvar rmail-deleted-vector nil)
473 (put 'rmail-deleted-vector 'permanent-local t)
474
475 (defvar rmail-msgref-vector nil
476 "In an Rmail buffer, a vector whose Nth element is a list (N).
477 When expunging renumbers messages, these lists are modified
478 by substituting the new message number into the existing list.")
479 (put 'rmail-msgref-vector 'permanent-local t)
480
481 (defvar rmail-overlay-list nil)
482 (put 'rmail-overlay-list 'permanent-local t)
483
484 ;; These are used by autoloaded rmail-summary.
485
486 (defvar rmail-summary-buffer nil)
487 (put 'rmail-summary-buffer 'permanent-local t)
488 (defvar rmail-summary-vector nil)
489 (put 'rmail-summary-vector 'permanent-local t)
490
491 (defvar rmail-view-buffer nil
492 "Buffer which holds RMAIL message for MIME displaying.")
493 (put 'rmail-view-buffer 'permanent-local t)
494 \f
495 ;; `Sticky' default variables.
496
497 ;; Last individual label specified to a or k.
498 (defvar rmail-last-label nil)
499 (put 'rmail-last-label 'permanent-local t)
500
501 ;; Last set of values specified to C-M-n, C-M-p, C-M-s or C-M-l.
502 (defvar rmail-last-multi-labels nil)
503
504 (defvar rmail-last-regexp nil)
505 (put 'rmail-last-regexp 'permanent-local t)
506
507 (defcustom rmail-default-file "~/xmail"
508 "*Default file name for \\[rmail-output]."
509 :type 'file
510 :group 'rmail-files)
511 (defcustom rmail-default-rmail-file "~/XMAIL"
512 "*Default file name for \\[rmail-output-to-rmail-file]."
513 :type 'file
514 :group 'rmail-files)
515 (defcustom rmail-default-body-file "~/mailout"
516 "*Default file name for \\[rmail-output-body-to-file]."
517 :type 'file
518 :group 'rmail-files
519 :version "20.3")
520
521 ;; Mule and MIME related variables.
522
523 ;;;###autoload
524 (defvar rmail-file-coding-system nil
525 "Coding system used in RMAIL file.
526
527 This is set to nil by default.")
528
529 ;;;###autoload
530 (defcustom rmail-enable-mime nil
531 "*If non-nil, RMAIL uses MIME feature.
532 If the value is t, RMAIL automatically shows MIME decoded message.
533 If the value is neither t nor nil, RMAIL does not show MIME decoded message
534 until a user explicitly requires it."
535 :type '(choice (const :tag "on" t)
536 (const :tag "off" nil)
537 (other :tag "when asked" ask))
538 :group 'rmail)
539
540 (defvar rmail-enable-mime-composing nil
541 "*If non-nil, RMAIL uses `rmail-insert-mime-forwarded-message-function' to forward.")
542
543 ;;;###autoload
544 (defvar rmail-show-mime-function nil
545 "Function to show MIME decoded message of RMAIL file.
546 This function is called when `rmail-enable-mime' is non-nil.
547 It is called with no argument.")
548
549 ;;;###autoload
550 (defvar rmail-insert-mime-forwarded-message-function nil
551 "Function to insert a message in MIME format so it can be forwarded.
552 This function is called if `rmail-enable-mime' or
553 `rmail-enable-mime-composing' is non-nil.
554 It is called with one argument FORWARD-BUFFER, which is a
555 buffer containing the message to forward. The current buffer
556 is the outgoing mail buffer.")
557
558 ;;;###autoload
559 (defvar rmail-insert-mime-resent-message-function nil
560 "Function to insert a message in MIME format so it can be resent.
561 This function is called if `rmail-enable-mime' is non-nil.
562 It is called with one argument FORWARD-BUFFER, which is a
563 buffer containing the message to forward. The current buffer
564 is the outgoing mail buffer.")
565
566 ;;;###autoload
567 (defvar rmail-search-mime-message-function nil
568 "Function to check if a regexp matches a MIME message.
569 This function is called if `rmail-enable-mime' is non-nil.
570 It is called with two arguments MSG and REGEXP, where
571 MSG is the message number, REGEXP is the regular expression.")
572
573 ;;;###autoload
574 (defvar rmail-search-mime-header-function nil
575 "Function to check if a regexp matches a header of MIME message.
576 This function is called if `rmail-enable-mime' is non-nil.
577 It is called with three arguments MSG, REGEXP, and LIMIT, where
578 MSG is the message number,
579 REGEXP is the regular expression,
580 LIMIT is the position specifying the end of header.")
581
582 ;;;###autoload
583 (defvar rmail-mime-feature 'rmail-mime
584 "Feature to require to load MIME support in Rmail.
585 When starting Rmail, if `rmail-enable-mime' is non-nil,
586 this feature is required with `require'.")
587
588 ;;;###autoload
589 (defvar rmail-decode-mime-charset t
590 "*Non-nil means a message is decoded by MIME's charset specification.
591 If this variable is nil, or the message has not MIME specification,
592 the message is decoded as normal way.
593
594 If the variable `rmail-enable-mime' is non-nil, this variables is
595 ignored, and all the decoding work is done by a feature specified by
596 the variable `rmail-mime-feature'.")
597
598 ;;;###autoload
599 (defvar rmail-mime-charset-pattern
600 "^content-type:[ ]*text/plain;[ \t\n]*charset=\"?\\([^ \t\n\";]+\\)\"?"
601 "Regexp to match MIME-charset specification in a header of message.
602 The first parenthesized expression should match the MIME-charset name.")
603
604 \f
605 ;;; Regexp matching the delimiter of messages in UNIX mail format
606 ;;; (UNIX From lines), minus the initial ^. Note that if you change
607 ;;; this expression, you must change the code in rmail-nuke-pinhead-header
608 ;;; that knows the exact ordering of the \\( \\) subexpressions.
609 (defvar rmail-unix-mail-delimiter
610 (let ((time-zone-regexp
611 (concat "\\([A-Z]?[A-Z]?[A-Z][A-Z]\\( DST\\)?"
612 "\\|[-+]?[0-9][0-9][0-9][0-9]"
613 "\\|"
614 "\\) *")))
615 (concat
616 "From "
617
618 ;; Many things can happen to an RFC 822 mailbox before it is put into
619 ;; a `From' line. The leading phrase can be stripped, e.g.
620 ;; `Joe <@w.x:joe@y.z>' -> `<@w.x:joe@y.z>'. The <> can be stripped, e.g.
621 ;; `<@x.y:joe@y.z>' -> `@x.y:joe@y.z'. Everything starting with a CRLF
622 ;; can be removed, e.g.
623 ;; From: joe@y.z (Joe K
624 ;; User)
625 ;; can yield `From joe@y.z (Joe K Fri Mar 22 08:11:15 1996', and
626 ;; From: Joe User
627 ;; <joe@y.z>
628 ;; can yield `From Joe User Fri Mar 22 08:11:15 1996'.
629 ;; The mailbox can be removed or be replaced by white space, e.g.
630 ;; From: "Joe User"{space}{tab}
631 ;; <joe@y.z>
632 ;; can yield `From {space}{tab} Fri Mar 22 08:11:15 1996',
633 ;; where {space} and {tab} represent the Ascii space and tab characters.
634 ;; We want to match the results of any of these manglings.
635 ;; The following regexp rejects names whose first characters are
636 ;; obviously bogus, but after that anything goes.
637 "\\([^\0-\b\n-\r\^?].*\\)? "
638
639 ;; The time the message was sent.
640 "\\([^\0-\r \^?]+\\) +" ; day of the week
641 "\\([^\0-\r \^?]+\\) +" ; month
642 "\\([0-3]?[0-9]\\) +" ; day of month
643 "\\([0-2][0-9]:[0-5][0-9]\\(:[0-6][0-9]\\)?\\) *" ; time of day
644
645 ;; Perhaps a time zone, specified by an abbreviation, or by a
646 ;; numeric offset.
647 time-zone-regexp
648
649 ;; The year.
650 " \\([0-9][0-9]+\\) *"
651
652 ;; On some systems the time zone can appear after the year, too.
653 time-zone-regexp
654
655 ;; Old uucp cruft.
656 "\\(remote from .*\\)?"
657
658 "\n"))
659 nil)
660
661 (defvar rmail-font-lock-keywords
662 ;; These are all matched case-insensitively.
663 (eval-when-compile
664 (let* ((cite-chars "[>|}]")
665 (cite-prefix "a-z")
666 (cite-suffix (concat cite-prefix "0-9_.@-`'\"")))
667 (list '("^\\(From\\|Sender\\|Resent-From\\):"
668 . font-lock-function-name-face)
669 '("^Reply-To:.*$" . font-lock-function-name-face)
670 '("^Subject:" . font-lock-comment-face)
671 '("^X-Spam-Status:" . font-lock-keyword-face)
672 '("^\\(To\\|Apparently-To\\|Cc\\|Newsgroups\\):"
673 . font-lock-keyword-face)
674 ;; Use MATCH-ANCHORED to effectively anchor the regexp left side.
675 `(,cite-chars
676 (,(concat "\\=[ \t]*"
677 "\\(\\(\\([" cite-prefix "]+[" cite-suffix "]*\\)?"
678 "\\(" cite-chars "[ \t]*\\)\\)+\\)"
679 "\\(.*\\)")
680 (beginning-of-line) (end-of-line)
681 (1 font-lock-comment-delimiter-face nil t)
682 (5 font-lock-comment-face nil t)))
683 '("^\\(X-[a-z0-9-]+\\|In-reply-to\\|Date\\):.*\\(\n[ \t]+.*\\)*$"
684 . font-lock-string-face))))
685 "Additional expressions to highlight in Rmail mode.")
686
687 ;; Perform BODY in the summary buffer
688 ;; in such a way that its cursor is properly updated in its own window.
689 (defmacro rmail-select-summary (&rest body)
690 `(let ((total rmail-total-messages))
691 (if (rmail-summary-displayed)
692 (let ((window (selected-window)))
693 (save-excursion
694 (unwind-protect
695 (progn
696 (pop-to-buffer rmail-summary-buffer)
697 ;; rmail-total-messages is a buffer-local var
698 ;; in the rmail buffer.
699 ;; This way we make it available for the body
700 ;; even tho the rmail buffer is not current.
701 (let ((rmail-total-messages total))
702 ,@body))
703 (select-window window))))
704 (save-excursion
705 (set-buffer rmail-summary-buffer)
706 (let ((rmail-total-messages total))
707 ,@body)))
708 (rmail-maybe-display-summary)))
709 \f
710 ;;;; *** Rmail Mode ***
711
712 ;; This variable is dynamically bound. The defvar is here to placate
713 ;; the byte compiler.
714
715 (defvar rmail-enable-multibyte nil)
716
717
718 (defun rmail-require-mime-maybe ()
719 "Require `rmail-mime-feature' if that is non-nil.
720 Signal an error and set `rmail-mime-feature' to nil if the feature
721 isn't provided."
722 (when rmail-enable-mime
723 (condition-case err
724 (require rmail-mime-feature)
725 (error
726 (message "Feature `%s' not provided" rmail-mime-feature)
727 (sit-for 1)
728 (setq rmail-enable-mime nil)))))
729
730
731 ;;;###autoload
732 (defun rmail (&optional file-name-arg)
733 "Read and edit incoming mail.
734 Moves messages into file named by `rmail-file-name' (a babyl format file)
735 and edits that file in RMAIL Mode.
736 Type \\[describe-mode] once editing that file, for a list of RMAIL commands.
737
738 May be called with file name as argument; then performs rmail editing on
739 that file, but does not copy any new mail into the file.
740 Interactively, if you supply a prefix argument, then you
741 have a chance to specify a file name with the minibuffer.
742
743 If `rmail-display-summary' is non-nil, make a summary for this RMAIL file."
744 (interactive (if current-prefix-arg
745 (list (read-file-name "Run rmail on RMAIL file: "))))
746 (rmail-require-mime-maybe)
747 (let* ((file-name (expand-file-name (or file-name-arg rmail-file-name)))
748 ;; Use find-buffer-visiting, not get-file-buffer, for those users
749 ;; who have find-file-visit-truename set to t.
750 (existed (find-buffer-visiting file-name))
751 ;; This binding is necessary because we must decide if we
752 ;; need code conversion while the buffer is unibyte
753 ;; (i.e. enable-multibyte-characters is nil).
754 (rmail-enable-multibyte
755 (if existed
756 (with-current-buffer existed enable-multibyte-characters)
757 (default-value 'enable-multibyte-characters)))
758 ;; Since the file may contain messages of different encodings
759 ;; at the tail (non-BYBYL part), we can't decode them at once
760 ;; on reading. So, at first, we read the file without text
761 ;; code conversion, then decode the messages one by one by
762 ;; rmail-decode-babyl-format or
763 ;; rmail-convert-to-babyl-format.
764 (coding-system-for-read (and rmail-enable-multibyte 'raw-text))
765 run-mail-hook msg-shown)
766 ;; Like find-file, but in the case where a buffer existed
767 ;; and the file was reverted, recompute the message-data.
768 ;; We used to bind enable-local-variables to nil here,
769 ;; but that should not be needed now that rmail-mode
770 ;; sets it locally to nil.
771 ;; (Binding a variable locally with let is not safe if it has
772 ;; buffer-local bindings.)
773 (if (and existed (not (verify-visited-file-modtime existed)))
774 (progn
775 (find-file file-name)
776 (if (and (verify-visited-file-modtime existed)
777 (eq major-mode 'rmail-mode))
778 (progn (rmail-forget-messages)
779 (rmail-set-message-counters))))
780 (switch-to-buffer
781 (let ((enable-local-variables nil))
782 (find-file-noselect file-name))))
783 (if (eq major-mode 'rmail-edit-mode)
784 (error "Exit Rmail Edit mode before getting new mail"))
785 (if (and existed (> (buffer-size) 0))
786 ;; Buffer not new and not empty; ensure in proper mode, but that's all.
787 (or (eq major-mode 'rmail-mode)
788 (progn (rmail-mode-2)
789 (setq run-mail-hook t)))
790 (setq run-mail-hook t)
791 (rmail-mode-2)
792 ;; Convert all or part to Babyl file if possible.
793 (rmail-convert-file)
794 (goto-char (point-max)))
795 ;; As we have read a file by raw-text, the buffer is set to
796 ;; unibyte. We must make it multibyte if necessary.
797 (if (and rmail-enable-multibyte
798 (not enable-multibyte-characters))
799 (set-buffer-multibyte t))
800 ;; If necessary, scan to find all the messages.
801 (rmail-maybe-set-message-counters)
802 (unwind-protect
803 (unless (and (not file-name-arg)
804 (rmail-get-new-mail))
805 (rmail-show-message (rmail-first-unseen-message)))
806 (progn
807 (if rmail-display-summary (rmail-summary))
808 (rmail-construct-io-menu)
809 (if run-mail-hook
810 (run-hooks 'rmail-mode-hook))))))
811
812 ;; Given the value of MAILPATH, return a list of inbox file names.
813 ;; This is turned off because it is not clear that the user wants
814 ;; all these inboxes to feed into the primary rmail file.
815 ; (defun rmail-convert-mailpath (string)
816 ; (let (idx list)
817 ; (while (setq idx (string-match "[%:]" string))
818 ; (let ((this (substring string 0 idx)))
819 ; (setq string (substring string (1+ idx)))
820 ; (setq list (cons (if (string-match "%" this)
821 ; (substring this 0 (string-match "%" this))
822 ; this)
823 ; list))))
824 ; list))
825
826 ; I have checked that adding "-*- rmail -*-" to the BABYL OPTIONS line
827 ; will not cause emacs 18.55 problems.
828
829 ;; This calls rmail-decode-babyl-format if the file is already Babyl.
830
831 (defun rmail-convert-file ()
832 (let (convert)
833 (widen)
834 (goto-char (point-min))
835 ;; If file doesn't start like a Babyl file,
836 ;; convert it to one, by adding a header and converting each message.
837 (cond ((looking-at "BABYL OPTIONS:"))
838 ((looking-at "Version: 5\n")
839 ;; Losing babyl file made by old version of Rmail.
840 ;; Just fix the babyl file header; don't make a new one,
841 ;; so we don't lose the Labels: file attribute, etc.
842 (let ((buffer-read-only nil))
843 (insert "BABYL OPTIONS: -*- rmail -*-\n")))
844 ((equal (point-min) (point-max))
845 ;; Empty RMAIL file. Just insert the header.
846 (rmail-insert-rmail-file-header))
847 (t
848 ;; Non-empty file in non-RMAIL format. Add header and convert.
849 (setq convert t)
850 (rmail-insert-rmail-file-header)))
851 ;; If file was not a Babyl file or if there are
852 ;; Unix format messages added at the end,
853 ;; convert file as necessary.
854 (if (or convert
855 (save-excursion
856 (goto-char (point-max))
857 (search-backward "\n\^_")
858 (forward-char 2)
859 (looking-at "\n*From ")))
860 (let ((buffer-read-only nil))
861 (message "Converting to Babyl format...")
862 ;; If file needs conversion, convert it all,
863 ;; except for the BABYL header.
864 ;; (rmail-convert-to-babyl-format would delete the header.)
865 (goto-char (point-min))
866 (search-forward "\n\^_" nil t)
867 (narrow-to-region (point) (point-max))
868 (rmail-convert-to-babyl-format)
869 (message "Converting to Babyl format...done"))
870 (if (and (not rmail-enable-mime)
871 rmail-enable-multibyte)
872 ;; We still have to decode BABYL part.
873 (rmail-decode-babyl-format)))))
874
875 (defun rmail-insert-rmail-file-header ()
876 (let ((buffer-read-only nil))
877 ;; -*-rmail-*- is here so that visiting the file normally
878 ;; recognizes it as an Rmail file.
879 (insert "BABYL OPTIONS: -*- rmail -*-
880 Version: 5
881 Labels:
882 Note: This is the header of an rmail file.
883 Note: If you are seeing it in rmail,
884 Note: it means the file has no messages in it.\n\^_")))
885
886 ;; Decode Babyl formatted part at the head of current buffer by
887 ;; rmail-file-coding-system, or if it is nil, do auto conversion.
888
889 (defun rmail-decode-babyl-format ()
890 (let ((modifiedp (buffer-modified-p))
891 (buffer-read-only nil)
892 (coding-system rmail-file-coding-system)
893 from to)
894 (goto-char (point-min))
895 (search-forward "\n\^_" nil t) ; Skip BABYL header.
896 (setq from (point))
897 (goto-char (point-max))
898 (search-backward "\n\^_" from 'mv)
899 (setq to (point))
900 (unless (and coding-system
901 (coding-system-p coding-system))
902 (setq coding-system
903 ;; Emacs 21.1 and later writes RMAIL files in emacs-mule, but
904 ;; earlier versions did that with the current buffer's encoding.
905 ;; So we want to favor detection of emacs-mule (whose normal
906 ;; priority is quite low), but still allow detection of other
907 ;; encodings if emacs-mule won't fit. The call to
908 ;; detect-coding-with-priority below achieves that.
909 (car (detect-coding-with-priority
910 from to
911 '((coding-category-emacs-mule . emacs-mule))))))
912 (unless (memq coding-system
913 '(undecided undecided-unix))
914 (set-buffer-modified-p t) ; avoid locking when decoding
915 (let ((buffer-undo-list t))
916 (decode-coding-region from to coding-system))
917 (setq coding-system last-coding-system-used))
918 (set-buffer-modified-p modifiedp)
919 (setq buffer-file-coding-system nil)
920 (setq save-buffer-coding-system
921 (or coding-system 'undecided))))
922
923 (defvar rmail-mode-map nil)
924 (if rmail-mode-map
925 nil
926 (setq rmail-mode-map (make-keymap))
927 (suppress-keymap rmail-mode-map)
928 (define-key rmail-mode-map "a" 'rmail-add-label)
929 (define-key rmail-mode-map "b" 'rmail-bury)
930 (define-key rmail-mode-map "c" 'rmail-continue)
931 (define-key rmail-mode-map "d" 'rmail-delete-forward)
932 (define-key rmail-mode-map "\C-d" 'rmail-delete-backward)
933 (define-key rmail-mode-map "e" 'rmail-edit-current-message)
934 (define-key rmail-mode-map "f" 'rmail-forward)
935 (define-key rmail-mode-map "g" 'rmail-get-new-mail)
936 (define-key rmail-mode-map "h" 'rmail-summary)
937 (define-key rmail-mode-map "i" 'rmail-input)
938 (define-key rmail-mode-map "j" 'rmail-show-message)
939 (define-key rmail-mode-map "k" 'rmail-kill-label)
940 (define-key rmail-mode-map "l" 'rmail-summary-by-labels)
941 (define-key rmail-mode-map "\e\C-h" 'rmail-summary)
942 (define-key rmail-mode-map "\e\C-l" 'rmail-summary-by-labels)
943 (define-key rmail-mode-map "\e\C-r" 'rmail-summary-by-recipients)
944 (define-key rmail-mode-map "\e\C-s" 'rmail-summary-by-regexp)
945 (define-key rmail-mode-map "\e\C-t" 'rmail-summary-by-topic)
946 (define-key rmail-mode-map "m" 'rmail-mail)
947 (define-key rmail-mode-map "\em" 'rmail-retry-failure)
948 (define-key rmail-mode-map "n" 'rmail-next-undeleted-message)
949 (define-key rmail-mode-map "\en" 'rmail-next-message)
950 (define-key rmail-mode-map "\e\C-n" 'rmail-next-labeled-message)
951 (define-key rmail-mode-map "o" 'rmail-output-to-rmail-file)
952 (define-key rmail-mode-map "\C-o" 'rmail-output)
953 (define-key rmail-mode-map "p" 'rmail-previous-undeleted-message)
954 (define-key rmail-mode-map "\ep" 'rmail-previous-message)
955 (define-key rmail-mode-map "\e\C-p" 'rmail-previous-labeled-message)
956 (define-key rmail-mode-map "q" 'rmail-quit)
957 (define-key rmail-mode-map "r" 'rmail-reply)
958 ;; I find I can't live without the default M-r command -- rms.
959 ;; (define-key rmail-mode-map "\er" 'rmail-search-backwards)
960 (define-key rmail-mode-map "s" 'rmail-expunge-and-save)
961 (define-key rmail-mode-map "\es" 'rmail-search)
962 (define-key rmail-mode-map "t" 'rmail-toggle-header)
963 (define-key rmail-mode-map "u" 'rmail-undelete-previous-message)
964 (define-key rmail-mode-map "w" 'rmail-output-body-to-file)
965 (define-key rmail-mode-map "x" 'rmail-expunge)
966 (define-key rmail-mode-map "." 'rmail-beginning-of-message)
967 (define-key rmail-mode-map "/" 'rmail-end-of-message)
968 (define-key rmail-mode-map "<" 'rmail-first-message)
969 (define-key rmail-mode-map ">" 'rmail-last-message)
970 (define-key rmail-mode-map " " 'scroll-up)
971 (define-key rmail-mode-map "\177" 'scroll-down)
972 (define-key rmail-mode-map "?" 'describe-mode)
973 (define-key rmail-mode-map "\C-c\C-s\C-d" 'rmail-sort-by-date)
974 (define-key rmail-mode-map "\C-c\C-s\C-s" 'rmail-sort-by-subject)
975 (define-key rmail-mode-map "\C-c\C-s\C-a" 'rmail-sort-by-author)
976 (define-key rmail-mode-map "\C-c\C-s\C-r" 'rmail-sort-by-recipient)
977 (define-key rmail-mode-map "\C-c\C-s\C-c" 'rmail-sort-by-correspondent)
978 (define-key rmail-mode-map "\C-c\C-s\C-l" 'rmail-sort-by-lines)
979 (define-key rmail-mode-map "\C-c\C-s\C-k" 'rmail-sort-by-labels)
980 (define-key rmail-mode-map "\C-c\C-n" 'rmail-next-same-subject)
981 (define-key rmail-mode-map "\C-c\C-p" 'rmail-previous-same-subject)
982 )
983 \f
984 (define-key rmail-mode-map [menu-bar] (make-sparse-keymap))
985
986 (define-key rmail-mode-map [menu-bar classify]
987 (cons "Classify" (make-sparse-keymap "Classify")))
988
989 (define-key rmail-mode-map [menu-bar classify input-menu]
990 nil)
991
992 (define-key rmail-mode-map [menu-bar classify output-menu]
993 nil)
994
995 (define-key rmail-mode-map [menu-bar classify output-body]
996 '("Output body to file..." . rmail-output-body-to-file))
997
998 (define-key rmail-mode-map [menu-bar classify output-inbox]
999 '("Output (inbox)..." . rmail-output))
1000
1001 (define-key rmail-mode-map [menu-bar classify output]
1002 '("Output (Rmail)..." . rmail-output-to-rmail-file))
1003
1004 (define-key rmail-mode-map [menu-bar classify kill-label]
1005 '("Kill Label..." . rmail-kill-label))
1006
1007 (define-key rmail-mode-map [menu-bar classify add-label]
1008 '("Add Label..." . rmail-add-label))
1009
1010 (define-key rmail-mode-map [menu-bar summary]
1011 (cons "Summary" (make-sparse-keymap "Summary")))
1012
1013 (define-key rmail-mode-map [menu-bar summary senders]
1014 '("By Senders..." . rmail-summary-by-senders))
1015
1016 (define-key rmail-mode-map [menu-bar summary labels]
1017 '("By Labels..." . rmail-summary-by-labels))
1018
1019 (define-key rmail-mode-map [menu-bar summary recipients]
1020 '("By Recipients..." . rmail-summary-by-recipients))
1021
1022 (define-key rmail-mode-map [menu-bar summary topic]
1023 '("By Topic..." . rmail-summary-by-topic))
1024
1025 (define-key rmail-mode-map [menu-bar summary regexp]
1026 '("By Regexp..." . rmail-summary-by-regexp))
1027
1028 (define-key rmail-mode-map [menu-bar summary all]
1029 '("All" . rmail-summary))
1030
1031 (define-key rmail-mode-map [menu-bar mail]
1032 (cons "Mail" (make-sparse-keymap "Mail")))
1033
1034 (define-key rmail-mode-map [menu-bar mail rmail-get-new-mail]
1035 '("Get New Mail" . rmail-get-new-mail))
1036
1037 (define-key rmail-mode-map [menu-bar mail lambda]
1038 '("----"))
1039
1040 (define-key rmail-mode-map [menu-bar mail continue]
1041 '("Continue" . rmail-continue))
1042
1043 (define-key rmail-mode-map [menu-bar mail resend]
1044 '("Re-send..." . rmail-resend))
1045
1046 (define-key rmail-mode-map [menu-bar mail forward]
1047 '("Forward" . rmail-forward))
1048
1049 (define-key rmail-mode-map [menu-bar mail retry]
1050 '("Retry" . rmail-retry-failure))
1051
1052 (define-key rmail-mode-map [menu-bar mail reply]
1053 '("Reply" . rmail-reply))
1054
1055 (define-key rmail-mode-map [menu-bar mail mail]
1056 '("Mail" . rmail-mail))
1057
1058 (define-key rmail-mode-map [menu-bar delete]
1059 (cons "Delete" (make-sparse-keymap "Delete")))
1060
1061 (define-key rmail-mode-map [menu-bar delete expunge/save]
1062 '("Expunge/Save" . rmail-expunge-and-save))
1063
1064 (define-key rmail-mode-map [menu-bar delete expunge]
1065 '("Expunge" . rmail-expunge))
1066
1067 (define-key rmail-mode-map [menu-bar delete undelete]
1068 '("Undelete" . rmail-undelete-previous-message))
1069
1070 (define-key rmail-mode-map [menu-bar delete delete]
1071 '("Delete" . rmail-delete-forward))
1072
1073 (define-key rmail-mode-map [menu-bar move]
1074 (cons "Move" (make-sparse-keymap "Move")))
1075
1076 (define-key rmail-mode-map [menu-bar move search-back]
1077 '("Search Back..." . rmail-search-backwards))
1078
1079 (define-key rmail-mode-map [menu-bar move search]
1080 '("Search..." . rmail-search))
1081
1082 (define-key rmail-mode-map [menu-bar move previous]
1083 '("Previous Nondeleted" . rmail-previous-undeleted-message))
1084
1085 (define-key rmail-mode-map [menu-bar move next]
1086 '("Next Nondeleted" . rmail-next-undeleted-message))
1087
1088 (define-key rmail-mode-map [menu-bar move last]
1089 '("Last" . rmail-last-message))
1090
1091 (define-key rmail-mode-map [menu-bar move first]
1092 '("First" . rmail-first-message))
1093
1094 (define-key rmail-mode-map [menu-bar move previous]
1095 '("Previous" . rmail-previous-message))
1096
1097 (define-key rmail-mode-map [menu-bar move next]
1098 '("Next" . rmail-next-message))
1099 \f
1100 ;; Rmail mode is suitable only for specially formatted data.
1101 (put 'rmail-mode 'mode-class 'special)
1102
1103 (defun rmail-mode-kill-summary ()
1104 (if rmail-summary-buffer (kill-buffer rmail-summary-buffer)))
1105
1106 ;;;###autoload
1107 (defun rmail-mode ()
1108 "Rmail Mode is used by \\<rmail-mode-map>\\[rmail] for editing Rmail files.
1109 All normal editing commands are turned off.
1110 Instead, these commands are available:
1111
1112 \\[rmail-beginning-of-message] Move point to front of this message.
1113 \\[rmail-end-of-message] Move point to bottom of this message.
1114 \\[scroll-up] Scroll to next screen of this message.
1115 \\[scroll-down] Scroll to previous screen of this message.
1116 \\[rmail-next-undeleted-message] Move to Next non-deleted message.
1117 \\[rmail-previous-undeleted-message] Move to Previous non-deleted message.
1118 \\[rmail-next-message] Move to Next message whether deleted or not.
1119 \\[rmail-previous-message] Move to Previous message whether deleted or not.
1120 \\[rmail-first-message] Move to the first message in Rmail file.
1121 \\[rmail-last-message] Move to the last message in Rmail file.
1122 \\[rmail-show-message] Jump to message specified by numeric position in file.
1123 \\[rmail-search] Search for string and show message it is found in.
1124 \\[rmail-delete-forward] Delete this message, move to next nondeleted.
1125 \\[rmail-delete-backward] Delete this message, move to previous nondeleted.
1126 \\[rmail-undelete-previous-message] Undelete message. Tries current message, then earlier messages
1127 till a deleted message is found.
1128 \\[rmail-edit-current-message] Edit the current message. \\[rmail-cease-edit] to return to Rmail.
1129 \\[rmail-expunge] Expunge deleted messages.
1130 \\[rmail-expunge-and-save] Expunge and save the file.
1131 \\[rmail-quit] Quit Rmail: expunge, save, then switch to another buffer.
1132 \\[save-buffer] Save without expunging.
1133 \\[rmail-get-new-mail] Move new mail from system spool directory into this file.
1134 \\[rmail-mail] Mail a message (same as \\[mail-other-window]).
1135 \\[rmail-continue] Continue composing outgoing message started before.
1136 \\[rmail-reply] Reply to this message. Like \\[rmail-mail] but initializes some fields.
1137 \\[rmail-retry-failure] Send this message again. Used on a mailer failure message.
1138 \\[rmail-forward] Forward this message to another user.
1139 \\[rmail-output-to-rmail-file] Output this message to an Rmail file (append it).
1140 \\[rmail-output] Output this message to a Unix-format mail file (append it).
1141 \\[rmail-output-body-to-file] Save message body to a file. Default filename comes from Subject line.
1142 \\[rmail-input] Input Rmail file. Run Rmail on that file.
1143 \\[rmail-add-label] Add label to message. It will be displayed in the mode line.
1144 \\[rmail-kill-label] Kill label. Remove a label from current message.
1145 \\[rmail-next-labeled-message] Move to Next message with specified label
1146 (label defaults to last one specified).
1147 Standard labels: filed, unseen, answered, forwarded, deleted.
1148 Any other label is present only if you add it with \\[rmail-add-label].
1149 \\[rmail-previous-labeled-message] Move to Previous message with specified label
1150 \\[rmail-summary] Show headers buffer, with a one line summary of each message.
1151 \\[rmail-summary-by-labels] Summarize only messages with particular label(s).
1152 \\[rmail-summary-by-recipients] Summarize only messages with particular recipient(s).
1153 \\[rmail-summary-by-regexp] Summarize only messages with particular regexp(s).
1154 \\[rmail-summary-by-topic] Summarize only messages with subject line regexp(s).
1155 \\[rmail-toggle-header] Toggle display of complete header."
1156 (interactive)
1157 (let ((finding-rmail-file (not (eq major-mode 'rmail-mode))))
1158 (rmail-mode-2)
1159 (when (and finding-rmail-file
1160 (null coding-system-for-read)
1161 default-enable-multibyte-characters)
1162 (let ((rmail-enable-multibyte t))
1163 (rmail-require-mime-maybe)
1164 (rmail-convert-file)
1165 (goto-char (point-max))
1166 (set-buffer-multibyte t)))
1167 (rmail-set-message-counters)
1168 (rmail-show-message rmail-total-messages)
1169 (when finding-rmail-file
1170 (when rmail-display-summary
1171 (rmail-summary))
1172 (rmail-construct-io-menu))
1173 (run-mode-hooks 'rmail-mode-hook)))
1174
1175 (defun rmail-mode-2 ()
1176 (kill-all-local-variables)
1177 (rmail-mode-1)
1178 (rmail-perm-variables)
1179 (rmail-variables))
1180
1181 (defun rmail-mode-1 ()
1182 (setq major-mode 'rmail-mode)
1183 (setq mode-name "RMAIL")
1184 (setq buffer-read-only t)
1185 ;; No need to auto save RMAIL files in normal circumstances
1186 ;; because they contain no info except attribute changes
1187 ;; and deletion of messages.
1188 ;; The one exception is when messages are copied into an Rmail mode buffer.
1189 ;; rmail-output-to-rmail-file enables auto save when you do that.
1190 (setq buffer-auto-save-file-name nil)
1191 (setq mode-line-modified "--")
1192 (use-local-map rmail-mode-map)
1193 (set-syntax-table text-mode-syntax-table)
1194 (setq local-abbrev-table text-mode-abbrev-table))
1195
1196 ;; Set up the permanent locals associated with an Rmail file.
1197 (defun rmail-perm-variables ()
1198 (make-local-variable 'rmail-last-label)
1199 (make-local-variable 'rmail-last-regexp)
1200 (make-local-variable 'rmail-deleted-vector)
1201 (make-local-variable 'rmail-buffer)
1202 (setq rmail-buffer (current-buffer))
1203 (make-local-variable 'rmail-view-buffer)
1204 (setq rmail-view-buffer rmail-buffer)
1205 (make-local-variable 'rmail-summary-buffer)
1206 (make-local-variable 'rmail-summary-vector)
1207 (make-local-variable 'rmail-current-message)
1208 (make-local-variable 'rmail-total-messages)
1209 (make-local-variable 'rmail-overlay-list)
1210 (setq rmail-overlay-list nil)
1211 (make-local-variable 'rmail-message-vector)
1212 (make-local-variable 'rmail-msgref-vector)
1213 (make-local-variable 'rmail-inbox-list)
1214 (setq rmail-inbox-list (rmail-parse-file-inboxes))
1215 ;; Provide default set of inboxes for primary mail file ~/RMAIL.
1216 (and (null rmail-inbox-list)
1217 (or (equal buffer-file-name (expand-file-name rmail-file-name))
1218 (equal buffer-file-truename
1219 (abbreviate-file-name (file-truename rmail-file-name))))
1220 (setq rmail-inbox-list
1221 (or rmail-primary-inbox-list
1222 (list (or (getenv "MAIL")
1223 (concat rmail-spool-directory
1224 (user-login-name)))))))
1225 (make-local-variable 'rmail-keywords)
1226 ;; this gets generated as needed
1227 (setq rmail-keywords nil))
1228
1229 ;; Set up the non-permanent locals associated with Rmail mode.
1230 (defun rmail-variables ()
1231 (make-local-variable 'save-buffer-coding-system)
1232 ;; If we don't already have a value for save-buffer-coding-system,
1233 ;; get it from buffer-file-coding-system, and clear that
1234 ;; because it should be determined in rmail-show-message.
1235 (unless save-buffer-coding-system
1236 (setq save-buffer-coding-system (or buffer-file-coding-system 'undecided))
1237 (setq buffer-file-coding-system nil))
1238 ;; Don't let a local variables list in a message cause confusion.
1239 (make-local-variable 'local-enable-local-variables)
1240 (setq local-enable-local-variables nil)
1241 (make-local-variable 'revert-buffer-function)
1242 (setq revert-buffer-function 'rmail-revert)
1243 (make-local-variable 'font-lock-defaults)
1244 (setq font-lock-defaults
1245 '(rmail-font-lock-keywords
1246 t t nil nil
1247 (font-lock-maximum-size . nil)
1248 (font-lock-fontify-buffer-function . rmail-fontify-buffer-function)
1249 (font-lock-unfontify-buffer-function . rmail-unfontify-buffer-function)
1250 (font-lock-inhibit-thing-lock . (lazy-lock-mode fast-lock-mode))))
1251 (make-local-variable 'require-final-newline)
1252 (setq require-final-newline nil)
1253 (make-local-variable 'version-control)
1254 (setq version-control 'never)
1255 (make-local-variable 'kill-buffer-hook)
1256 (add-hook 'kill-buffer-hook 'rmail-mode-kill-summary)
1257 (make-local-variable 'file-precious-flag)
1258 (setq file-precious-flag t)
1259 (make-local-variable 'desktop-save-buffer)
1260 (setq desktop-save-buffer t))
1261
1262 ;; Handle M-x revert-buffer done in an rmail-mode buffer.
1263 (defun rmail-revert (arg noconfirm)
1264 (set-buffer rmail-buffer)
1265 (let* ((revert-buffer-function (default-value 'revert-buffer-function))
1266 (rmail-enable-multibyte enable-multibyte-characters)
1267 ;; See similar code in `rmail'.
1268 (coding-system-for-read (and rmail-enable-multibyte 'raw-text)))
1269 ;; Call our caller again, but this time it does the default thing.
1270 (if (revert-buffer arg noconfirm)
1271 ;; If the user said "yes", and we changed something,
1272 ;; reparse the messages.
1273 (progn
1274 (set-buffer rmail-buffer)
1275 (rmail-mode-2)
1276 ;; Convert all or part to Babyl file if possible.
1277 (rmail-convert-file)
1278 ;; We have read the file as raw-text, so the buffer is set to
1279 ;; unibyte. Make it multibyte if necessary.
1280 (if (and rmail-enable-multibyte
1281 (not enable-multibyte-characters))
1282 (set-buffer-multibyte t))
1283 (goto-char (point-max))
1284 (rmail-set-message-counters)
1285 (rmail-show-message rmail-total-messages)
1286 (run-hooks 'rmail-mode-hook)))))
1287
1288 ;; Return a list of files from this buffer's Mail: option.
1289 ;; Does not assume that messages have been parsed.
1290 ;; Just returns nil if buffer does not look like Babyl format.
1291 (defun rmail-parse-file-inboxes ()
1292 (save-excursion
1293 (save-restriction
1294 (widen)
1295 (goto-char 1)
1296 (cond ((looking-at "BABYL OPTIONS:")
1297 (search-forward "\n\^_" nil 'move)
1298 (narrow-to-region 1 (point))
1299 (goto-char 1)
1300 (if (search-forward "\nMail:" nil t)
1301 (progn
1302 (narrow-to-region (point) (progn (end-of-line) (point)))
1303 (goto-char (point-min))
1304 (mail-parse-comma-list))))))))
1305
1306 (defun rmail-expunge-and-save ()
1307 "Expunge and save RMAIL file."
1308 (interactive)
1309 (rmail-expunge)
1310 (set-buffer rmail-buffer)
1311 (save-buffer)
1312 (if (rmail-summary-exists)
1313 (rmail-select-summary (set-buffer-modified-p nil))))
1314
1315 (defun rmail-quit ()
1316 "Quit out of RMAIL.
1317 Hook `rmail-quit-hook' is run after expunging."
1318 (interactive)
1319 (rmail-expunge-and-save)
1320 (when (boundp 'rmail-quit-hook)
1321 (run-hooks 'rmail-quit-hook))
1322 ;; Don't switch to the summary buffer even if it was recently visible.
1323 (when rmail-summary-buffer
1324 (replace-buffer-in-windows rmail-summary-buffer)
1325 (bury-buffer rmail-summary-buffer))
1326 (if rmail-enable-mime
1327 (let ((obuf rmail-buffer)
1328 (ovbuf rmail-view-buffer))
1329 (set-buffer rmail-view-buffer)
1330 (quit-window)
1331 (replace-buffer-in-windows ovbuf)
1332 (replace-buffer-in-windows obuf)
1333 (bury-buffer obuf))
1334 (let ((obuf (current-buffer)))
1335 (quit-window)
1336 (replace-buffer-in-windows obuf))))
1337
1338 (defun rmail-bury ()
1339 "Bury current Rmail buffer and its summary buffer."
1340 (interactive)
1341 ;; This let var was called rmail-buffer, but that interfered
1342 ;; with the buffer-local var used in summary buffers.
1343 (let ((buffer-to-bury (current-buffer)))
1344 (if (rmail-summary-exists)
1345 (let (window)
1346 (while (setq window (get-buffer-window rmail-summary-buffer))
1347 (quit-window nil window))
1348 (bury-buffer rmail-summary-buffer)))
1349 (quit-window)))
1350
1351 (defun rmail-duplicate-message ()
1352 "Create a duplicated copy of the current message.
1353 The duplicate copy goes into the Rmail file just after the
1354 original copy."
1355 (interactive)
1356 (widen)
1357 (let ((buffer-read-only nil)
1358 (number rmail-current-message)
1359 (string (buffer-substring (rmail-msgbeg rmail-current-message)
1360 (rmail-msgend rmail-current-message))))
1361 (goto-char (rmail-msgend rmail-current-message))
1362 (insert string)
1363 (rmail-forget-messages)
1364 (rmail-show-message number)
1365 (message "Message duplicated")))
1366
1367 ;;;###autoload
1368 (defun rmail-input (filename)
1369 "Run Rmail on file FILENAME."
1370 (interactive "FRun rmail on RMAIL file: ")
1371 (rmail filename))
1372
1373
1374 ;; This used to scan subdirectories recursively, but someone pointed out
1375 ;; that if the user wants that, person can put all the files in one dir.
1376 ;; And the recursive scan was slow. So I took it out.
1377 ;; rms, Sep 1996.
1378 (defun rmail-find-all-files (start)
1379 "Return list of file in dir START that match `rmail-secondary-file-regexp'."
1380 (if (file-accessible-directory-p start)
1381 ;; Don't sort here.
1382 (let* ((case-fold-search t)
1383 (files (directory-files start t rmail-secondary-file-regexp)))
1384 ;; Sort here instead of in directory-files
1385 ;; because this list is usually much shorter.
1386 (sort files 'string<))))
1387
1388 (defun rmail-list-to-menu (menu-name l action &optional full-name)
1389 (let ((menu (make-sparse-keymap menu-name)))
1390 (mapcar
1391 (function (lambda (item)
1392 (let (command)
1393 (if (consp item)
1394 (progn
1395 (setq command
1396 (rmail-list-to-menu (car item) (cdr item)
1397 action
1398 (if full-name
1399 (concat full-name "/"
1400 (car item))
1401 (car item))))
1402 (setq name (car item)))
1403 (progn
1404 (setq name item)
1405 (setq command
1406 (list 'lambda () '(interactive)
1407 (list action
1408 (expand-file-name
1409 (if full-name
1410 (concat full-name "/" item)
1411 item)
1412 rmail-secondary-file-directory))))))
1413 (define-key menu (vector (intern name))
1414 (cons name command)))))
1415 (reverse l))
1416 menu))
1417
1418 ;; This command is always "disabled" when it appears in a menu.
1419 (put 'rmail-disable-menu 'menu-enable ''nil)
1420
1421 (defun rmail-construct-io-menu ()
1422 (let ((files (rmail-find-all-files rmail-secondary-file-directory)))
1423 (if files
1424 (progn
1425 (define-key rmail-mode-map [menu-bar classify input-menu]
1426 (cons "Input Rmail File"
1427 (rmail-list-to-menu "Input Rmail File"
1428 files
1429 'rmail-input)))
1430 (define-key rmail-mode-map [menu-bar classify output-menu]
1431 (cons "Output Rmail File"
1432 (rmail-list-to-menu "Output Rmail File"
1433 files
1434 'rmail-output-to-rmail-file))))
1435
1436 (define-key rmail-mode-map [menu-bar classify input-menu]
1437 '("Input Rmail File" . rmail-disable-menu))
1438 (define-key rmail-mode-map [menu-bar classify output-menu]
1439 '("Output Rmail File" . rmail-disable-menu)))))
1440
1441 \f
1442 ;;;; *** Rmail input ***
1443
1444 ;; RLK feature not added in this version:
1445 ;; argument specifies inbox file or files in various ways.
1446
1447 (defun rmail-get-new-mail (&optional file-name)
1448 "Move any new mail from this RMAIL file's inbox files.
1449 The inbox files can be specified with the file's Mail: option. The
1450 variable `rmail-primary-inbox-list' specifies the inboxes for your
1451 primary RMAIL file if it has no Mail: option. By default, this is
1452 your /usr/spool/mail/$USER.
1453
1454 You can also specify the file to get new mail from. In this case, the
1455 file of new mail is not changed or deleted. Noninteractively, you can
1456 pass the inbox file name as an argument. Interactively, a prefix
1457 argument causes us to read a file name and use that file as the inbox.
1458
1459 If the variable `rmail-preserve-inbox' is non-nil, new mail will
1460 always be left in inbox files rather than deleted.
1461
1462 This function runs `rmail-get-new-mail-hook' before saving the updated file.
1463 It returns t if it got any new messages."
1464 (interactive
1465 (list (if current-prefix-arg
1466 (read-file-name "Get new mail from file: "))))
1467 (run-hooks 'rmail-before-get-new-mail-hook)
1468 ;; If the disk file has been changed from under us,
1469 ;; revert to it before we get new mail.
1470 (or (verify-visited-file-modtime (current-buffer))
1471 (find-file (buffer-file-name)))
1472 (set-buffer rmail-buffer)
1473 (rmail-maybe-set-message-counters)
1474 (widen)
1475 ;; Get rid of all undo records for this buffer.
1476 (or (eq buffer-undo-list t)
1477 (setq buffer-undo-list nil))
1478 (let ((all-files (if file-name (list file-name)
1479 rmail-inbox-list))
1480 (rmail-enable-multibyte (default-value 'enable-multibyte-characters))
1481 found)
1482 (unwind-protect
1483 (progn
1484 (while all-files
1485 (let ((opoint (point))
1486 (new-messages 0)
1487 (rsf-number-of-spam 0)
1488 (delete-files ())
1489 ;; If buffer has not changed yet, and has not been saved yet,
1490 ;; don't replace the old backup file now.
1491 (make-backup-files (and make-backup-files (buffer-modified-p)))
1492 (buffer-read-only nil)
1493 ;; Don't make undo records for what we do in getting mail.
1494 (buffer-undo-list t)
1495 success
1496 ;; Files to insert this time around.
1497 files
1498 ;; Last names of those files.
1499 file-last-names)
1500 ;; Pull files off all-files onto files
1501 ;; as long as there is no name conflict.
1502 ;; A conflict happens when two inbox file names
1503 ;; have the same last component.
1504 (while (and all-files
1505 (not (member (file-name-nondirectory (car all-files))
1506 file-last-names)))
1507 (setq files (cons (car all-files) files)
1508 file-last-names
1509 (cons (file-name-nondirectory (car all-files)) files))
1510 (setq all-files (cdr all-files)))
1511 ;; Put them back in their original order.
1512 (setq files (nreverse files))
1513
1514 (goto-char (point-max))
1515 (skip-chars-backward " \t\n") ; just in case of brain damage
1516 (delete-region (point) (point-max)) ; caused by require-final-newline
1517 (save-excursion
1518 (save-restriction
1519 (narrow-to-region (point) (point))
1520 ;; Read in the contents of the inbox files,
1521 ;; renaming them as necessary,
1522 ;; and adding to the list of files to delete eventually.
1523 (if file-name
1524 (rmail-insert-inbox-text files nil)
1525 (setq delete-files (rmail-insert-inbox-text files t)))
1526 ;; Scan the new text and convert each message to babyl format.
1527 (goto-char (point-min))
1528 (unwind-protect
1529 (save-excursion
1530 (setq new-messages (rmail-convert-to-babyl-format)
1531 success t))
1532 ;; Try to delete the garbage just inserted.
1533 (or success (delete-region (point-min) (point-max)))
1534 ;; If we could not convert the file's inboxes,
1535 ;; rename the files we tried to read
1536 ;; so we won't over and over again.
1537 (if (and (not file-name) (not success))
1538 (let ((delfiles delete-files)
1539 (count 0))
1540 (while delfiles
1541 (while (file-exists-p (format "RMAILOSE.%d" count))
1542 (setq count (1+ count)))
1543 (rename-file (car delfiles)
1544 (format "RMAILOSE.%d" count))
1545 (setq delfiles (cdr delfiles))))))
1546 (or (zerop new-messages)
1547 (let (success)
1548 (widen)
1549 (search-backward "\n\^_" nil t)
1550 (narrow-to-region (point) (point-max))
1551 (goto-char (1+ (point-min)))
1552 (rmail-count-new-messages)
1553 (run-hooks 'rmail-get-new-mail-hook)
1554 (save-buffer)))
1555 ;; Delete the old files, now that babyl file is saved.
1556 (while delete-files
1557 (condition-case ()
1558 ;; First, try deleting.
1559 (condition-case ()
1560 (delete-file (car delete-files))
1561 (file-error
1562 ;; If we can't delete it, truncate it.
1563 (write-region (point) (point) (car delete-files))))
1564 (file-error nil))
1565 (setq delete-files (cdr delete-files)))))
1566 (if (= new-messages 0)
1567 (progn (goto-char opoint)
1568 (if (or file-name rmail-inbox-list)
1569 (message "(No new mail has arrived)")))
1570 ;; check new messages to see if any of them is spam:
1571 (if (and (featurep 'rmail-spam-filter)
1572 rmail-use-spam-filter)
1573 (let*
1574 ((old-messages (- rmail-total-messages new-messages))
1575 (rsf-scanned-message-number (1+ old-messages))
1576 ;; save deletion flags of old messages: vector starts
1577 ;; at zero (is one longer that no of messages),
1578 ;; therefore take 1+ old-messages
1579 (save-deleted
1580 (substring rmail-deleted-vector 0 (1+
1581 old-messages))))
1582 ;; set all messages to undeleted
1583 (setq rmail-deleted-vector
1584 (make-string (1+ rmail-total-messages) ?\ ))
1585 (while (<= rsf-scanned-message-number
1586 rmail-total-messages)
1587 (progn
1588 (if (not (rmail-spam-filter rsf-scanned-message-number))
1589 (progn (setq rsf-number-of-spam (1+ rsf-number-of-spam)))
1590 )
1591 (setq rsf-scanned-message-number (1+ rsf-scanned-message-number))
1592 ))
1593 (if (> rsf-number-of-spam 0)
1594 (progn
1595 (when (rmail-expunge-confirmed)
1596 (rmail-only-expunge t))
1597 ))
1598 (setq rmail-deleted-vector
1599 (concat
1600 save-deleted
1601 (make-string (- rmail-total-messages old-messages)
1602 ?\ )))
1603 ))
1604 (if (rmail-summary-exists)
1605 (rmail-select-summary
1606 (rmail-update-summary)))
1607 (message "%d new message%s read%s"
1608 new-messages (if (= 1 new-messages) "" "s")
1609 ;; print out a message on number of spam messages found:
1610 (if (and (featurep 'rmail-spam-filter)
1611 rmail-use-spam-filter
1612 (> rsf-number-of-spam 0))
1613 (if (= 1 new-messages)
1614 ", and found to be a spam message"
1615 (if (> rsf-number-of-spam 1)
1616 (format ", %d of which found to be spam messages"
1617 rsf-number-of-spam)
1618 ", one of which found to be a spam message"))
1619 ""))
1620 (if (and (featurep 'rmail-spam-filter)
1621 rmail-use-spam-filter
1622 (> rsf-number-of-spam 0))
1623 (progn (if rsf-beep (beep t))
1624 (sleep-for rsf-sleep-after-message)))
1625
1626 ;; Move to the first new message
1627 ;; unless we have other unseen messages before it.
1628 (rmail-show-message (rmail-first-unseen-message))
1629 (run-hooks 'rmail-after-get-new-mail-hook)
1630 (setq found t))))
1631 found)
1632 ;; Don't leave the buffer screwed up if we get a disk-full error.
1633 (or found (rmail-show-message)))))
1634
1635 (defun rmail-parse-url (file)
1636 "Parse the supplied URL. Return (list MAILBOX-NAME REMOTE PASSWORD GOT-PASSWORD)
1637 WHERE MAILBOX-NAME is the name of the mailbox suitable as argument to the
1638 actual version of `movemail', REMOTE is non-nil if MAILBOX-NAME refers to
1639 a remote mailbox, PASSWORD is the password if it should be
1640 supplied as a separate argument to `movemail' or nil otherwise, GOT-PASSWORD
1641 is non-nil if the user has supplied the password interactively.
1642 "
1643 (cond
1644 ((string-match "^\\([^:]+\\)://\\(\\([^:@]+\\)\\(:\\([^@]+\\)\\)?@\\)?.*" file)
1645 (let (got-password supplied-password
1646 (proto (match-string 1 file))
1647 (user (match-string 3 file))
1648 (pass (match-string 5 file))
1649 (host (substring file (or (match-end 2)
1650 (+ 3 (match-end 1))))))
1651
1652 (if (not pass)
1653 (when rmail-remote-password-required
1654 (setq got-password (not (rmail-have-password)))
1655 (setq supplied-password (rmail-get-remote-password
1656 (string-equal proto "imap")))))
1657
1658 (if (rmail-movemail-variant-p 'emacs)
1659 (if (string-equal proto "pop")
1660 (list (concat "po:" user ":" host)
1661 t
1662 (or pass supplied-password)
1663 got-password)
1664 (error "Emacs movemail does not support %s protocol" proto))
1665 (list file
1666 (or (string-equal proto "pop") (string-equal proto "imap"))
1667 supplied-password
1668 got-password))))
1669
1670 ((string-match "^po:\\([^:]+\\)\\(:\\(.*\\)\\)?" file)
1671 (let (got-password supplied-password
1672 (proto "pop")
1673 (user (match-string 1 file))
1674 (host (match-string 3 file)))
1675
1676 (when rmail-remote-password-required
1677 (setq got-password (not (rmail-have-password)))
1678 (setq supplied-password (rmail-get-remote-password nil)))
1679
1680 (list file "pop" supplied-password got-password)))
1681
1682 (t
1683 (list file nil nil nil))))
1684
1685 (defun rmail-insert-inbox-text (files renamep)
1686 ;; Detect a locked file now, so that we avoid moving mail
1687 ;; out of the real inbox file. (That could scare people.)
1688 (or (memq (file-locked-p buffer-file-name) '(nil t))
1689 (error "RMAIL file %s is locked"
1690 (file-name-nondirectory buffer-file-name)))
1691 (let (file tofile delete-files movemail popmail got-password password)
1692 (while files
1693 ;; Handle remote mailbox names specially; don't expand as filenames
1694 ;; in case the userid contains a directory separator.
1695 (setq file (car files))
1696 (let ((url-data (rmail-parse-url file)))
1697 (setq file (nth 0 url-data))
1698 (setq popmail (nth 1 url-data))
1699 (setq password (nth 2 url-data))
1700 (setq got-password (nth 3 url-data)))
1701
1702 (if popmail
1703 (setq renamep t)
1704 (setq file (file-truename
1705 (substitute-in-file-name (expand-file-name file)))))
1706 (setq tofile (expand-file-name
1707 ;; Generate name to move to from inbox name,
1708 ;; in case of multiple inboxes that need moving.
1709 (concat ".newmail-"
1710 (file-name-nondirectory
1711 (if (memq system-type '(windows-nt cygwin))
1712 ;; cannot have "po:" in file name
1713 (substring file 3)
1714 file)))
1715 ;; Use the directory of this rmail file
1716 ;; because it's a nuisance to use the homedir
1717 ;; if that is on a full disk and this rmail
1718 ;; file isn't.
1719 (file-name-directory
1720 (expand-file-name buffer-file-name))))
1721 ;; Always use movemail to rename the file,
1722 ;; since there can be mailboxes in various directories.
1723 (if (not popmail)
1724 (progn
1725 ;; On some systems, /usr/spool/mail/foo is a directory
1726 ;; and the actual inbox is /usr/spool/mail/foo/foo.
1727 (if (file-directory-p file)
1728 (setq file (expand-file-name (user-login-name)
1729 file)))))
1730 (cond (popmail
1731 (message "Getting mail from the remote server ..."))
1732 ((and (file-exists-p tofile)
1733 (/= 0 (nth 7 (file-attributes tofile))))
1734 (message "Getting mail from %s..." tofile))
1735 ((and (file-exists-p file)
1736 (/= 0 (nth 7 (file-attributes file))))
1737 (message "Getting mail from %s..." file)))
1738 ;; Set TOFILE if have not already done so, and
1739 ;; rename or copy the file FILE to TOFILE if and as appropriate.
1740 (cond ((not renamep)
1741 (setq tofile file))
1742 ((or (file-exists-p tofile) (and (not popmail)
1743 (not (file-exists-p file))))
1744 nil)
1745 (t
1746 (with-temp-buffer
1747 (let ((errors (current-buffer)))
1748 (buffer-disable-undo errors)
1749 (let ((args
1750 (append
1751 (list (or rmail-movemail-program
1752 (expand-file-name "movemail"
1753 exec-directory))
1754 nil errors nil)
1755 (if rmail-preserve-inbox
1756 (list "-p")
1757 nil)
1758 (if (rmail-movemail-variant-p 'mailutils)
1759 (append (list "--emacs") rmail-movemail-flags)
1760 rmail-movemail-flags)
1761 (list file tofile)
1762 (if password (list password) nil))))
1763 (apply 'call-process args))
1764 (if (not (buffer-modified-p errors))
1765 ;; No output => movemail won
1766 nil
1767 (set-buffer errors)
1768 (subst-char-in-region (point-min) (point-max)
1769 ?\n ?\ )
1770 (goto-char (point-max))
1771 (skip-chars-backward " \t")
1772 (delete-region (point) (point-max))
1773 (goto-char (point-min))
1774 (if (looking-at "movemail: ")
1775 (delete-region (point-min) (match-end 0)))
1776 (beep t)
1777 ;; If we just read the password, most likely it is
1778 ;; wrong. Otherwise, see if there is a specific
1779 ;; reason to think that the problem is a wrong passwd.
1780 (if (or got-password
1781 (re-search-forward rmail-remote-password-error
1782 nil t))
1783 (rmail-set-remote-password nil))
1784
1785 ;; If using Mailutils, remove initial error code
1786 ;; abbreviation
1787 (when (rmail-movemail-variant-p 'mailutils)
1788 (goto-char (point-min))
1789 (when (looking-at "[A-Z][A-Z0-9_]*:")
1790 (delete-region (point-min) (match-end 0))))
1791
1792 (message "movemail: %s"
1793 (buffer-substring (point-min)
1794 (point-max)))
1795
1796 (sit-for 3)
1797 nil)))))
1798
1799 ;; At this point, TOFILE contains the name to read:
1800 ;; Either the alternate name (if we renamed)
1801 ;; or the actual inbox (if not renaming).
1802 (if (file-exists-p tofile)
1803 (let ((coding-system-for-read 'no-conversion)
1804 size)
1805 (goto-char (point-max))
1806 (setq size (nth 1 (insert-file-contents tofile)))
1807 (goto-char (point-max))
1808 (or (= (preceding-char) ?\n)
1809 (zerop size)
1810 (insert ?\n))
1811 (if (not (and rmail-preserve-inbox (string= file tofile)))
1812 (setq delete-files (cons tofile delete-files)))))
1813 (message "")
1814 (setq files (cdr files)))
1815 delete-files))
1816
1817 ;; Decode the region specified by FROM and TO by CODING.
1818 ;; If CODING is nil or an invalid coding system, decode by `undecided'.
1819 (defun rmail-decode-region (from to coding)
1820 (if (or (not coding) (not (coding-system-p coding)))
1821 (setq coding 'undecided))
1822 ;; Use -dos decoding, to remove ^M characters left from base64 or
1823 ;; rogue qp-encoded text.
1824 (decode-coding-region from to
1825 (coding-system-change-eol-conversion coding 1))
1826 ;; Don't reveal the fact we used -dos decoding, as users generally
1827 ;; will not expect the RMAIL buffer to use DOS EOL format.
1828 (setq buffer-file-coding-system
1829 (setq last-coding-system-used
1830 (coding-system-change-eol-conversion coding 0))))
1831
1832 ;; the rmail-break-forwarded-messages feature is not implemented
1833 (defun rmail-convert-to-babyl-format ()
1834 (let ((count 0) start
1835 (case-fold-search nil)
1836 (invalid-input-resync
1837 (function (lambda ()
1838 (message "Invalid Babyl format in inbox!")
1839 (sit-for 3)
1840 ;; Try to get back in sync with a real message.
1841 (if (re-search-forward
1842 (concat rmail-mmdf-delim1 "\\|^From") nil t)
1843 (beginning-of-line)
1844 (goto-char (point-max)))))))
1845 (goto-char (point-min))
1846 (save-restriction
1847 (while (not (eobp))
1848 (setq start (point))
1849 (cond ((looking-at "BABYL OPTIONS:");Babyl header
1850 (if (search-forward "\n\^_" nil t)
1851 ;; If we find the proper terminator, delete through there.
1852 (delete-region (point-min) (point))
1853 (funcall invalid-input-resync)
1854 (delete-region (point-min) (point))))
1855 ;; Babyl format message
1856 ((looking-at "\^L")
1857 (or (search-forward "\n\^_" nil t)
1858 (funcall invalid-input-resync))
1859 (setq count (1+ count))
1860 ;; Make sure there is no extra white space after the ^_
1861 ;; at the end of the message.
1862 ;; Narrowing will make sure that whatever follows the junk
1863 ;; will be treated properly.
1864 (delete-region (point)
1865 (save-excursion
1866 (skip-chars-forward " \t\n")
1867 (point)))
1868 (save-excursion
1869 (let* ((header-end
1870 (progn
1871 (save-excursion
1872 (goto-char start)
1873 (forward-line 1)
1874 (if (looking-at "0")
1875 (forward-line 1)
1876 (forward-line 2))
1877 (save-restriction
1878 (narrow-to-region (point) (point-max))
1879 (rfc822-goto-eoh)
1880 (point)))))
1881 (case-fold-search t)
1882 (quoted-printable-header-field-end
1883 (save-excursion
1884 (goto-char start)
1885 (re-search-forward
1886 "^content-transfer-encoding:\\(\n?[\t ]\\)*quoted-printable\\(\n?[\t ]\\)*"
1887 header-end t)))
1888 (base64-header-field-end
1889 (save-excursion
1890 (goto-char start)
1891 (re-search-forward
1892 "^content-transfer-encoding:\\(\n?[\t ]\\)*base64\\(\n?[\t ]\\)*"
1893 header-end t))))
1894 (if quoted-printable-header-field-end
1895 (save-excursion
1896 (unless
1897 (mail-unquote-printable-region header-end (point) nil t t)
1898 (message "Malformed MIME quoted-printable message"))
1899 ;; Change "quoted-printable" to "8bit",
1900 ;; to reflect the decoding we just did.
1901 (goto-char quoted-printable-header-field-end)
1902 (delete-region (point) (search-backward ":"))
1903 (insert ": 8bit")))
1904 (if base64-header-field-end
1905 (save-excursion
1906 (when
1907 (condition-case nil
1908 (progn
1909 (base64-decode-region (1+ header-end)
1910 (- (point) 2))
1911 t)
1912 (error nil))
1913 ;; Change "base64" to "8bit", to reflect the
1914 ;; decoding we just did.
1915 (goto-char base64-header-field-end)
1916 (delete-region (point) (search-backward ":"))
1917 (insert ": 8bit"))))
1918 (setq last-coding-system-used nil)
1919 (or rmail-enable-mime
1920 (not rmail-enable-multibyte)
1921 (let ((mime-charset
1922 (if (and rmail-decode-mime-charset
1923 (save-excursion
1924 (goto-char start)
1925 (search-forward "\n\n" nil t)
1926 (let ((case-fold-search t))
1927 (re-search-backward
1928 rmail-mime-charset-pattern
1929 start t))))
1930 (intern (downcase (match-string 1))))))
1931 (rmail-decode-region start (point) mime-charset)))))
1932 ;; Add an X-Coding-System: header if we don't have one.
1933 (save-excursion
1934 (goto-char start)
1935 (forward-line 1)
1936 (if (looking-at "0")
1937 (forward-line 1)
1938 (forward-line 2))
1939 (or (save-restriction
1940 (narrow-to-region (point) (point-max))
1941 (rfc822-goto-eoh)
1942 (goto-char (point-min))
1943 (re-search-forward "^X-Coding-System:" nil t))
1944 (insert "X-Coding-System: "
1945 (symbol-name last-coding-system-used)
1946 "\n")))
1947 (narrow-to-region (point) (point-max))
1948 (and (= 0 (% count 10))
1949 (message "Converting to Babyl format...%d" count)))
1950 ;;*** MMDF format
1951 ((let ((case-fold-search t))
1952 (looking-at rmail-mmdf-delim1))
1953 (let ((case-fold-search t))
1954 (replace-match "\^L\n0, unseen,,\n*** EOOH ***\n")
1955 (re-search-forward rmail-mmdf-delim2 nil t)
1956 (replace-match "\^_"))
1957 (save-excursion
1958 (save-restriction
1959 (narrow-to-region start (1- (point)))
1960 (goto-char (point-min))
1961 (while (search-forward "\n\^_" nil t); single char "\^_"
1962 (replace-match "\n^_")))); 2 chars: "^" and "_"
1963 (setq last-coding-system-used nil)
1964 (or rmail-enable-mime
1965 (not rmail-enable-multibyte)
1966 (decode-coding-region start (point) 'undecided))
1967 (save-excursion
1968 (goto-char start)
1969 (forward-line 3)
1970 (insert "X-Coding-System: "
1971 (symbol-name last-coding-system-used)
1972 "\n"))
1973 (narrow-to-region (point) (point-max))
1974 (setq count (1+ count))
1975 (and (= 0 (% count 10))
1976 (message "Converting to Babyl format...%d" count)))
1977 ;;*** Mail format
1978 ((looking-at "^From ")
1979 (insert "\^L\n0, unseen,,\n*** EOOH ***\n")
1980 (rmail-nuke-pinhead-header)
1981 ;; If this message has a Content-Length field,
1982 ;; skip to the end of the contents.
1983 (let* ((header-end (save-excursion
1984 (and (re-search-forward "\n\n" nil t)
1985 (1- (point)))))
1986 (case-fold-search t)
1987 (quoted-printable-header-field-end
1988 (save-excursion
1989 (re-search-forward
1990 "^content-transfer-encoding:\\(\n?[\t ]\\)*quoted-printable\\(\n?[\t ]\\)*"
1991 header-end t)))
1992 (base64-header-field-end
1993 (save-excursion
1994 (re-search-forward
1995 "^content-transfer-encoding:\\(\n?[\t ]\\)*base64\\(\n?[\t ]\\)*"
1996 header-end t)))
1997 (size
1998 ;; Get the numeric value from the Content-Length field.
1999 (save-excursion
2000 ;; Back up to end of prev line,
2001 ;; in case the Content-Length field comes first.
2002 (forward-char -1)
2003 (and (search-forward "\ncontent-length: "
2004 header-end t)
2005 (let ((beg (point))
2006 (eol (progn (end-of-line) (point))))
2007 (string-to-number (buffer-substring beg eol)))))))
2008 (and size
2009 (if (and (natnump size)
2010 (<= (+ header-end size) (point-max))
2011 ;; Make sure this would put us at a position
2012 ;; that we could continue from.
2013 (save-excursion
2014 (goto-char (+ header-end size))
2015 (skip-chars-forward "\n")
2016 (or (eobp)
2017 (and (looking-at "BABYL OPTIONS:")
2018 (search-forward "\n\^_" nil t))
2019 (and (looking-at "\^L")
2020 (search-forward "\n\^_" nil t))
2021 (let ((case-fold-search t))
2022 (looking-at rmail-mmdf-delim1))
2023 (looking-at "From "))))
2024 (goto-char (+ header-end size))
2025 (message "Ignoring invalid Content-Length field")
2026 (sit-for 1 0 t)))
2027 (if (let ((case-fold-search nil))
2028 (re-search-forward
2029 (concat "^[\^_]?\\("
2030 rmail-unix-mail-delimiter
2031 "\\|"
2032 rmail-mmdf-delim1 "\\|"
2033 "^BABYL OPTIONS:\\|"
2034 "\^L\n[01],\\)") nil t))
2035 (goto-char (match-beginning 1))
2036 (goto-char (point-max)))
2037 (setq count (1+ count))
2038 (if quoted-printable-header-field-end
2039 (save-excursion
2040 (unless
2041 (mail-unquote-printable-region header-end (point) nil t t)
2042 (message "Malformed MIME quoted-printable message"))
2043 ;; Change "quoted-printable" to "8bit",
2044 ;; to reflect the decoding we just did.
2045 (goto-char quoted-printable-header-field-end)
2046 (delete-region (point) (search-backward ":"))
2047 (insert ": 8bit")))
2048 (if base64-header-field-end
2049 (save-excursion
2050 (when
2051 (condition-case nil
2052 (progn
2053 (base64-decode-region
2054 (1+ header-end)
2055 (save-excursion
2056 ;; Prevent base64-decode-region
2057 ;; from removing newline characters.
2058 (skip-chars-backward "\n\t ")
2059 (point)))
2060 t)
2061 (error nil))
2062 ;; Change "base64" to "8bit", to reflect the
2063 ;; decoding we just did.
2064 (goto-char base64-header-field-end)
2065 (delete-region (point) (search-backward ":"))
2066 (insert ": 8bit")))))
2067
2068 (save-excursion
2069 (save-restriction
2070 (narrow-to-region start (point))
2071 (goto-char (point-min))
2072 (while (search-forward "\n\^_" nil t); single char
2073 (replace-match "\n^_")))); 2 chars: "^" and "_"
2074 ;; This is for malformed messages that don't end in newline.
2075 ;; There shouldn't be any, but some users say occasionally
2076 ;; there are some.
2077 (or (bolp) (newline))
2078 (insert ?\^_)
2079 (setq last-coding-system-used nil)
2080 (or rmail-enable-mime
2081 (not rmail-enable-multibyte)
2082 (let ((mime-charset
2083 (if (and rmail-decode-mime-charset
2084 (save-excursion
2085 (goto-char start)
2086 (search-forward "\n\n" nil t)
2087 (let ((case-fold-search t))
2088 (re-search-backward
2089 rmail-mime-charset-pattern
2090 start t))))
2091 (intern (downcase (match-string 1))))))
2092 (rmail-decode-region start (point) mime-charset)))
2093 (save-excursion
2094 (goto-char start)
2095 (forward-line 3)
2096 (insert "X-Coding-System: "
2097 (symbol-name last-coding-system-used)
2098 "\n"))
2099 (narrow-to-region (point) (point-max))
2100 (and (= 0 (% count 10))
2101 (message "Converting to Babyl format...%d" count)))
2102 ;;
2103 ;; This kludge is because some versions of sendmail.el
2104 ;; insert an extra newline at the beginning that shouldn't
2105 ;; be there. sendmail.el has been fixed, but old versions
2106 ;; may still be in use. -- rms, 7 May 1993.
2107 ((eolp) (delete-char 1))
2108 (t (error "Cannot convert to babyl format")))))
2109 count))
2110
2111 ;; Delete the "From ..." line, creating various other headers with
2112 ;; information from it if they don't already exist. Now puts the
2113 ;; original line into a mail-from: header line for debugging and for
2114 ;; use by the rmail-output function.
2115 (defun rmail-nuke-pinhead-header ()
2116 (save-excursion
2117 (save-restriction
2118 (let ((start (point))
2119 (end (progn
2120 (condition-case ()
2121 (search-forward "\n\n")
2122 (error
2123 (goto-char (point-max))
2124 (insert "\n\n")))
2125 (point)))
2126 has-from has-date)
2127 (narrow-to-region start end)
2128 (let ((case-fold-search t))
2129 (goto-char start)
2130 (setq has-from (search-forward "\nFrom:" nil t))
2131 (goto-char start)
2132 (setq has-date (and (search-forward "\nDate:" nil t) (point)))
2133 (goto-char start))
2134 (let ((case-fold-search nil))
2135 (if (re-search-forward (concat "^" rmail-unix-mail-delimiter) nil t)
2136 (replace-match
2137 (concat
2138 "Mail-from: \\&"
2139 ;; Keep and reformat the date if we don't
2140 ;; have a Date: field.
2141 (if has-date
2142 ""
2143 (concat
2144 "Date: \\2, \\4 \\3 \\9 \\5 "
2145
2146 ;; The timezone could be matched by group 7 or group 10.
2147 ;; If neither of them matched, assume EST, since only
2148 ;; Easterners would be so sloppy.
2149 ;; It's a shame the substitution can't use "\\10".
2150 (cond
2151 ((/= (match-beginning 7) (match-end 7)) "\\7")
2152 ((/= (match-beginning 10) (match-end 10))
2153 (buffer-substring (match-beginning 10)
2154 (match-end 10)))
2155 (t "EST"))
2156 "\n"))
2157 ;; Keep and reformat the sender if we don't
2158 ;; have a From: field.
2159 (if has-from
2160 ""
2161 "From: \\1\n"))
2162 t)))))))
2163 \f
2164 ;;;; *** Rmail Message Formatting and Header Manipulation ***
2165
2166 (defun rmail-reformat-message (beg end)
2167 (goto-char beg)
2168 (forward-line 1)
2169 (if (/= (following-char) ?0)
2170 (error "Bad format in RMAIL file"))
2171 (let ((inhibit-read-only t)
2172 (delta (- (buffer-size) end)))
2173 (delete-char 1)
2174 (insert ?1)
2175 (forward-line 1)
2176 (let ((case-fold-search t))
2177 (while (looking-at "Summary-line:\\|Mail-From:")
2178 (forward-line 1)))
2179 (if (looking-at "\\*\\*\\* EOOH \\*\\*\\*\n")
2180 (delete-region (point)
2181 (progn (forward-line 1) (point))))
2182 (let ((str (buffer-substring (point)
2183 (save-excursion (search-forward "\n\n" end 'move)
2184 (point)))))
2185 (insert str "*** EOOH ***\n")
2186 (narrow-to-region (point) (- (buffer-size) delta)))
2187 (goto-char (point-min))
2188 (if rmail-message-filter (funcall rmail-message-filter))
2189 (if (or rmail-displayed-headers rmail-ignored-headers)
2190 (rmail-clear-headers))))
2191
2192 (defun rmail-clear-headers (&optional ignored-headers)
2193 "Delete all header fields that Rmail should not show.
2194 If the optional argument IGNORED-HEADERS is non-nil,
2195 delete all header fields whose names match that regexp.
2196 Otherwise, if `rmail-displayed-headers' is non-nil,
2197 delete all header fields *except* those whose names match that regexp.
2198 Otherwise, delete all header fields whose names match `rmail-ignored-headers'
2199 unless they also match `rmail-nonignored-headers'."
2200 (when (search-forward "\n\n" nil t)
2201 (forward-char -1)
2202 (let ((case-fold-search t)
2203 (buffer-read-only nil))
2204 (if (and rmail-displayed-headers (null ignored-headers))
2205 (save-restriction
2206 (narrow-to-region (point-min) (point))
2207 (let (lim next)
2208 (goto-char (point-min))
2209 (while (and (not (eobp))
2210 (save-excursion
2211 (if (re-search-forward "\n[^ \t]" nil t)
2212 (setq lim (match-beginning 0)
2213 next (1+ lim))
2214 (setq lim nil next (point-max)))))
2215 (if (save-excursion
2216 (re-search-forward rmail-displayed-headers lim t))
2217 (goto-char next)
2218 (delete-region (point) next))))
2219 (goto-char (point-min)))
2220 (or ignored-headers (setq ignored-headers rmail-ignored-headers))
2221 (save-restriction
2222 (narrow-to-region (point-min) (point))
2223 (goto-char (point-min))
2224 (while (and ignored-headers
2225 (re-search-forward ignored-headers nil t))
2226 (beginning-of-line)
2227 (if (looking-at rmail-nonignored-headers)
2228 (forward-line 1)
2229 (delete-region (point)
2230 (save-excursion
2231 (if (re-search-forward "\n[^ \t]" nil t)
2232 (1- (point))
2233 (point-max)))))))))))
2234
2235 (defun rmail-msg-is-pruned ()
2236 (rmail-maybe-set-message-counters)
2237 (save-restriction
2238 (narrow-to-region (rmail-msgbeg rmail-current-message) (point-max))
2239 (save-excursion
2240 (goto-char (point-min))
2241 (forward-line 1)
2242 (= (following-char) ?1))))
2243
2244 (defun rmail-msg-restore-non-pruned-header ()
2245 (let ((old-point (point))
2246 new-point
2247 new-start
2248 (inhibit-read-only t))
2249 (save-excursion
2250 (narrow-to-region (rmail-msgbeg rmail-current-message) (point-max))
2251 (goto-char (point-min))
2252 (forward-line 1)
2253 ;; Change 1 to 0.
2254 (delete-char 1)
2255 (insert ?0)
2256 ;; Insert new EOOH line at the proper place.
2257 (forward-line 1)
2258 (let ((case-fold-search t))
2259 (while (looking-at "Summary-Line:\\|Mail-From:")
2260 (forward-line 1)))
2261 (insert "*** EOOH ***\n")
2262 (setq new-start (point))
2263 ;; Delete the old reformatted header.
2264 (forward-char -1)
2265 (search-forward "\n*** EOOH ***\n")
2266 (forward-line -1)
2267 (let ((start (point)))
2268 (search-forward "\n\n")
2269 (if (and (<= start old-point)
2270 (<= old-point (point)))
2271 (setq new-point new-start))
2272 (delete-region start (point)))
2273 ;; Narrow to after the new EOOH line.
2274 (narrow-to-region new-start (point-max)))
2275 (if new-point
2276 (goto-char new-point))))
2277
2278 (defun rmail-msg-prune-header ()
2279 (let ((new-point
2280 (= (point) (point-min))))
2281 (save-excursion
2282 (narrow-to-region (rmail-msgbeg rmail-current-message) (point-max))
2283 (rmail-reformat-message (point-min) (point-max)))
2284 (if new-point
2285 (goto-char (point-min)))))
2286
2287 (defun rmail-toggle-header (&optional arg)
2288 "Show original message header if pruned header currently shown, or vice versa.
2289 With argument ARG, show the message header pruned if ARG is greater than zero;
2290 otherwise, show it in full."
2291 (interactive "P")
2292 (let* ((pruned (with-current-buffer rmail-buffer
2293 (rmail-msg-is-pruned)))
2294 (prune (if arg
2295 (> (prefix-numeric-value arg) 0)
2296 (not pruned))))
2297 (if (eq pruned prune)
2298 t
2299 (set-buffer rmail-buffer)
2300 (rmail-maybe-set-message-counters)
2301 (if rmail-enable-mime
2302 (let ((buffer-read-only nil))
2303 (if pruned
2304 (rmail-msg-restore-non-pruned-header)
2305 (rmail-msg-prune-header))
2306 (funcall rmail-show-mime-function))
2307 (let* ((buffer-read-only nil)
2308 (window (get-buffer-window (current-buffer)))
2309 (at-point-min (= (point) (point-min)))
2310 (all-headers-visible (= (window-start window) (point-min)))
2311 (on-header
2312 (save-excursion
2313 (and (not (search-backward "\n\n" nil t))
2314 (progn
2315 (end-of-line)
2316 (re-search-backward "^[-A-Za-z0-9]+:" nil t))
2317 (match-string 0))))
2318 (old-screen-line
2319 (rmail-count-screen-lines (window-start window) (point))))
2320 (if pruned
2321 (rmail-msg-restore-non-pruned-header)
2322 (rmail-msg-prune-header))
2323 (cond (at-point-min
2324 (goto-char (point-min)))
2325 (on-header
2326 (goto-char (point-min))
2327 (search-forward "\n\n")
2328 (or (re-search-backward
2329 (concat "^" (regexp-quote on-header)) nil t)
2330 (goto-char (point-min))))
2331 (t
2332 (save-selected-window
2333 (select-window window)
2334 (recenter old-screen-line)
2335 (if (and all-headers-visible
2336 (not (= (window-start) (point-min))))
2337 (recenter (- (window-height) 2))))))))
2338 (rmail-highlight-headers))))
2339
2340 (defun rmail-narrow-to-non-pruned-header ()
2341 "Narrow to the whole (original) header of the current message."
2342 (let (start end)
2343 (narrow-to-region (rmail-msgbeg rmail-current-message) (point-max))
2344 (goto-char (point-min))
2345 (forward-line 1)
2346 (if (= (following-char) ?1)
2347 (progn
2348 (forward-line 1)
2349 (setq start (point))
2350 (search-forward "*** EOOH ***\n")
2351 (setq end (match-beginning 0)))
2352 (forward-line 2)
2353 (setq start (point))
2354 (search-forward "\n\n")
2355 (setq end (1- (point))))
2356 (narrow-to-region start end)
2357 (goto-char start)))
2358
2359 ;; Lifted from repos-count-screen-lines.
2360 ;; Return number of screen lines between START and END.
2361 (defun rmail-count-screen-lines (start end)
2362 (save-excursion
2363 (save-restriction
2364 (narrow-to-region start end)
2365 (goto-char (point-min))
2366 (vertical-motion (- (point-max) (point-min))))))
2367 \f
2368 ;;;; *** Rmail Attributes and Keywords ***
2369
2370 ;; Make a string describing current message's attributes and keywords
2371 ;; and set it up as the name of a minor mode
2372 ;; so it will appear in the mode line.
2373 (defun rmail-display-labels ()
2374 (let ((blurb "") (beg (point-min-marker)) (end (point-max-marker)))
2375 (save-excursion
2376 (unwind-protect
2377 (progn
2378 (widen)
2379 (goto-char (rmail-msgbeg rmail-current-message))
2380 (forward-line 1)
2381 (if (looking-at "[01],")
2382 (progn
2383 (narrow-to-region (point) (progn (end-of-line) (point)))
2384 ;; Truly valid BABYL format requires a space before each
2385 ;; attribute or keyword name. Put them in if missing.
2386 (let (buffer-read-only)
2387 (goto-char (point-min))
2388 (while (search-forward "," nil t)
2389 (or (looking-at "[ ,]") (eobp)
2390 (insert " "))))
2391 (goto-char (point-max))
2392 (if (search-backward ",," nil 'move)
2393 (progn
2394 (if (> (point) (1+ (point-min)))
2395 (setq blurb (buffer-substring (+ 1 (point-min)) (point))))
2396 (if (> (- (point-max) (point)) 2)
2397 (setq blurb
2398 (concat blurb
2399 ";"
2400 (buffer-substring (+ (point) 3)
2401 (1- (point-max)))))))))))
2402 ;; Note: we don't use save-restriction because that does not work right
2403 ;; if changes are made outside the saved restriction
2404 ;; before that restriction is restored.
2405 (narrow-to-region beg end)
2406 (set-marker beg nil)
2407 (set-marker end nil)))
2408 (while (string-match " +," blurb)
2409 (setq blurb (concat (substring blurb 0 (match-beginning 0)) ","
2410 (substring blurb (match-end 0)))))
2411 (while (string-match ", +" blurb)
2412 (setq blurb (concat (substring blurb 0 (match-beginning 0)) ","
2413 (substring blurb (match-end 0)))))
2414 (setq mode-line-process
2415 (format " %d/%d%s"
2416 rmail-current-message rmail-total-messages blurb))
2417 ;; If rmail-enable-mime is non-nil, we may have to update
2418 ;; `mode-line-process' of rmail-view-buffer too.
2419 (if (and rmail-enable-mime
2420 (not (eq (current-buffer) rmail-view-buffer))
2421 (buffer-live-p rmail-view-buffer))
2422 (let ((mlp mode-line-process))
2423 (with-current-buffer rmail-view-buffer
2424 (setq mode-line-process mlp))))))
2425
2426 ;; Turn an attribute of a message on or off according to STATE.
2427 ;; ATTR is the name of the attribute, as a string.
2428 ;; MSGNUM is message number to change; nil means current message.
2429 (defun rmail-set-attribute (attr state &optional msgnum)
2430 (set-buffer rmail-buffer)
2431 (let ((omax (point-max-marker))
2432 (omin (point-min-marker))
2433 (buffer-read-only nil))
2434 (or msgnum (setq msgnum rmail-current-message))
2435 (if (> msgnum 0)
2436 (unwind-protect
2437 (save-excursion
2438 (widen)
2439 (goto-char (+ 3 (rmail-msgbeg msgnum)))
2440 (let ((curstate
2441 (not
2442 (null (search-backward (concat ", " attr ",")
2443 (prog1 (point) (end-of-line)) t)))))
2444 (or (eq curstate (not (not state)))
2445 (if curstate
2446 (delete-region (point) (1- (match-end 0)))
2447 (beginning-of-line)
2448 (forward-char 2)
2449 (insert " " attr ","))))
2450 (if (string= attr "deleted")
2451 (rmail-set-message-deleted-p msgnum state)))
2452 ;; Note: we don't use save-restriction because that does not work right
2453 ;; if changes are made outside the saved restriction
2454 ;; before that restriction is restored.
2455 (narrow-to-region omin omax)
2456 (set-marker omin nil)
2457 (set-marker omax nil)
2458 (if (= msgnum rmail-current-message)
2459 (rmail-display-labels))))))
2460
2461 ;; Return t if the attributes/keywords line of msg number MSG
2462 ;; contains a match for the regexp LABELS.
2463 (defun rmail-message-labels-p (msg labels)
2464 (save-excursion
2465 (save-restriction
2466 (widen)
2467 (goto-char (rmail-msgbeg msg))
2468 (forward-char 3)
2469 (re-search-backward labels (prog1 (point) (end-of-line)) t))))
2470 \f
2471 ;;;; *** Rmail Message Selection And Support ***
2472
2473 (defun rmail-msgend (n)
2474 (marker-position (aref rmail-message-vector (1+ n))))
2475
2476 (defun rmail-msgbeg (n)
2477 (marker-position (aref rmail-message-vector n)))
2478
2479 (defun rmail-widen-to-current-msgbeg (function)
2480 "Call FUNCTION with point at start of internal data of current message.
2481 Assumes that bounds were previously narrowed to display the message in Rmail.
2482 The bounds are widened enough to move point where desired, then narrowed
2483 again afterward.
2484
2485 FUNCTION may not change the visible text of the message, but it may
2486 change the invisible header text."
2487 (save-excursion
2488 (unwind-protect
2489 (progn
2490 (narrow-to-region (rmail-msgbeg rmail-current-message)
2491 (point-max))
2492 (goto-char (point-min))
2493 (funcall function))
2494 ;; Note: we don't use save-restriction because that does not work right
2495 ;; if changes are made outside the saved restriction
2496 ;; before that restriction is restored.
2497 (narrow-to-region (rmail-msgbeg rmail-current-message)
2498 (rmail-msgend rmail-current-message)))))
2499
2500 (defun rmail-forget-messages ()
2501 (unwind-protect
2502 (if (vectorp rmail-message-vector)
2503 (let* ((i 0)
2504 (v rmail-message-vector)
2505 (n (length v)))
2506 (while (< i n)
2507 (move-marker (aref v i) nil)
2508 (setq i (1+ i)))))
2509 (setq rmail-message-vector nil)
2510 (setq rmail-msgref-vector nil)
2511 (setq rmail-deleted-vector nil)))
2512
2513 (defun rmail-maybe-set-message-counters ()
2514 (if (not (and rmail-deleted-vector
2515 rmail-message-vector
2516 rmail-current-message
2517 rmail-total-messages))
2518 (rmail-set-message-counters)))
2519
2520 (defun rmail-count-new-messages (&optional nomsg)
2521 (let* ((case-fold-search nil)
2522 (total-messages 0)
2523 (messages-head nil)
2524 (deleted-head nil))
2525 (or nomsg (message "Counting new messages..."))
2526 (goto-char (point-max))
2527 ;; Put at the end of messages-head
2528 ;; the entry for message N+1, which marks
2529 ;; the end of message N. (N = number of messages).
2530 (search-backward "\n\^_")
2531 (forward-char 1)
2532 (setq messages-head (list (point-marker)))
2533 (rmail-set-message-counters-counter (point-min))
2534 (setq rmail-current-message (1+ rmail-total-messages))
2535 (setq rmail-total-messages
2536 (+ rmail-total-messages total-messages))
2537 (setq rmail-message-vector
2538 (vconcat rmail-message-vector (cdr messages-head)))
2539 (aset rmail-message-vector
2540 rmail-current-message (car messages-head))
2541 (setq rmail-deleted-vector
2542 (concat rmail-deleted-vector deleted-head))
2543 (setq rmail-summary-vector
2544 (vconcat rmail-summary-vector (make-vector total-messages nil)))
2545 (setq rmail-msgref-vector
2546 (vconcat rmail-msgref-vector (make-vector total-messages nil)))
2547 ;; Fill in the new elements of rmail-msgref-vector.
2548 (let ((i (1+ (- rmail-total-messages total-messages))))
2549 (while (<= i rmail-total-messages)
2550 (aset rmail-msgref-vector i (list i))
2551 (setq i (1+ i))))
2552 (goto-char (point-min))
2553 (or nomsg (message "Counting new messages...done (%d)" total-messages))))
2554
2555 (defun rmail-set-message-counters ()
2556 (rmail-forget-messages)
2557 (save-excursion
2558 (save-restriction
2559 (widen)
2560 (let* ((point-save (point))
2561 (total-messages 0)
2562 (messages-after-point)
2563 (case-fold-search nil)
2564 (messages-head nil)
2565 (deleted-head nil))
2566 (message "Counting messages...")
2567 (goto-char (point-max))
2568 ;; Put at the end of messages-head
2569 ;; the entry for message N+1, which marks
2570 ;; the end of message N. (N = number of messages).
2571 (search-backward "\n\^_" nil t)
2572 (if (/= (point) (point-max)) (forward-char 1))
2573 (setq messages-head (list (point-marker)))
2574 (rmail-set-message-counters-counter (min (point) point-save))
2575 (setq messages-after-point total-messages)
2576 (rmail-set-message-counters-counter)
2577 (setq rmail-total-messages total-messages)
2578 (setq rmail-current-message
2579 (min total-messages
2580 (max 1 (- total-messages messages-after-point))))
2581 (setq rmail-message-vector
2582 (apply 'vector (cons (point-min-marker) messages-head))
2583 rmail-deleted-vector (concat "0" deleted-head)
2584 rmail-summary-vector (make-vector rmail-total-messages nil)
2585 rmail-msgref-vector (make-vector (1+ rmail-total-messages) nil))
2586 (let ((i 0))
2587 (while (<= i rmail-total-messages)
2588 (aset rmail-msgref-vector i (list i))
2589 (setq i (1+ i))))
2590 (message "Counting messages...done")))))
2591
2592 (defun rmail-set-message-counters-counter (&optional stop)
2593 (let ((start (point))
2594 next)
2595 (while (search-backward "\n\^_\^L" stop t)
2596 ;; Detect messages that have been added with DOS line endings and
2597 ;; convert the line endings for such messages.
2598 (setq next (point))
2599 (if (looking-at "\n\^_\^L\r\n")
2600 (let ((buffer-read-only nil)
2601 (buffer-undo t))
2602 (message "Counting messages...(converting line endings)")
2603 (save-excursion
2604 (goto-char start)
2605 (while (search-backward "\r\n" next t)
2606 (delete-char 1)))))
2607 (setq start next)
2608 (forward-char 1)
2609 (setq messages-head (cons (point-marker) messages-head))
2610 (save-excursion
2611 (setq deleted-head
2612 (cons (if (search-backward ", deleted,"
2613 (prog1 (point)
2614 (forward-line 2))
2615 t)
2616 ?D ?\ )
2617 deleted-head)))
2618 (if (zerop (% (setq total-messages (1+ total-messages)) 20))
2619 (message "Counting messages...%d" total-messages)))))
2620
2621 (defun rmail-beginning-of-message ()
2622 "Show current message starting from the beginning."
2623 (interactive)
2624 (let ((rmail-show-message-hook
2625 (list (function (lambda ()
2626 (goto-char (point-min)))))))
2627 (rmail-show-message rmail-current-message)))
2628
2629 (defun rmail-end-of-message ()
2630 "Show bottom of current message."
2631 (interactive)
2632 (let ((rmail-show-message-hook
2633 (list (function (lambda ()
2634 (goto-char (point-max))
2635 (recenter (1- (window-height))))))))
2636 (rmail-show-message rmail-current-message)))
2637
2638 (defun rmail-unknown-mail-followup-to ()
2639 "Handle a \"Mail-Followup-To\" header field with an unknown mailing list.
2640 Ask the user whether to add that list name to `mail-mailing-lists'."
2641 (save-restriction
2642 (rmail-narrow-to-non-pruned-header)
2643 (let ((mail-followup-to (mail-fetch-field "mail-followup-to" nil t)))
2644 (when mail-followup-to
2645 (let ((addresses
2646 (split-string
2647 (mail-strip-quoted-names mail-followup-to)
2648 ",[[:space:]]+" t)))
2649 (dolist (addr addresses)
2650 (when (and (not (member addr mail-mailing-lists))
2651 (not
2652 ;; taken from rmailsum.el
2653 (string-match
2654 (or rmail-user-mail-address-regexp
2655 (concat "^\\("
2656 (regexp-quote (user-login-name))
2657 "\\($\\|@\\)\\|"
2658 (regexp-quote
2659 (or user-mail-address
2660 (concat (user-login-name) "@"
2661 (or mail-host-address
2662 (system-name)))))
2663 "\\>\\)"))
2664 addr))
2665 (y-or-n-p
2666 (format "Add `%s' to `mail-mailing-lists'? "
2667 addr)))
2668 (customize-save-variable 'mail-mailing-lists
2669 (cons addr mail-mailing-lists)))))))))
2670
2671 (defun rmail-show-message (&optional n no-summary)
2672 "Show message number N (prefix argument), counting from start of file.
2673 If summary buffer is currently displayed, update current message there also."
2674 (interactive "p")
2675 (or (eq major-mode 'rmail-mode)
2676 (switch-to-buffer rmail-buffer))
2677 (rmail-maybe-set-message-counters)
2678 (widen)
2679 (if (zerop rmail-total-messages)
2680 (progn (narrow-to-region (point-min) (1- (point-max)))
2681 (goto-char (point-min))
2682 (setq mode-line-process nil))
2683 (let (blurb coding-system)
2684 (if (not n)
2685 (setq n rmail-current-message)
2686 (cond ((<= n 0)
2687 (setq n 1
2688 rmail-current-message 1
2689 blurb "No previous message"))
2690 ((> n rmail-total-messages)
2691 (setq n rmail-total-messages
2692 rmail-current-message rmail-total-messages
2693 blurb "No following message"))
2694 (t
2695 (setq rmail-current-message n))))
2696 (let ((beg (rmail-msgbeg n)))
2697 (goto-char beg)
2698 (forward-line 1)
2699 (save-excursion
2700 (let ((end (rmail-msgend n)))
2701 (save-restriction
2702 (if (prog1 (= (following-char) ?0)
2703 (forward-line 2)
2704 ;; If there's a Summary-line in the (otherwise empty)
2705 ;; header, we didn't yet get past the EOOH line.
2706 (if (looking-at "^\\*\\*\\* EOOH \\*\\*\\*\n")
2707 (forward-line 1))
2708 (narrow-to-region (point) end))
2709 (rfc822-goto-eoh)
2710 (search-forward "\n*** EOOH ***\n" end t))
2711 (narrow-to-region beg (point))
2712 (goto-char (point-min))
2713 (if (re-search-forward "^X-Coding-System: *\\(.*\\)$" nil t)
2714 (let ((coding-system (intern (match-string 1))))
2715 (condition-case nil
2716 (progn
2717 (check-coding-system coding-system)
2718 (setq buffer-file-coding-system coding-system))
2719 (error
2720 (setq buffer-file-coding-system nil))))
2721 (setq buffer-file-coding-system nil)))))
2722 ;; Clear the "unseen" attribute when we show a message.
2723 (rmail-set-attribute "unseen" nil)
2724 (let ((end (rmail-msgend n)))
2725 ;; Reformat the header, or else find the reformatted header.
2726 (if (= (following-char) ?0)
2727 (rmail-reformat-message beg end)
2728 (search-forward "\n*** EOOH ***\n" end t)
2729 (narrow-to-region (point) end)))
2730 (goto-char (point-min))
2731 (walk-windows
2732 (function (lambda (window)
2733 (if (eq (window-buffer window) (current-buffer))
2734 (set-window-point window (point)))))
2735 nil t)
2736 (rmail-display-labels)
2737 (if (eq rmail-enable-mime t)
2738 (funcall rmail-show-mime-function)
2739 (setq rmail-view-buffer rmail-buffer))
2740 (when mail-mailing-lists
2741 (rmail-unknown-mail-followup-to))
2742 (rmail-highlight-headers)
2743 (if transient-mark-mode (deactivate-mark))
2744 (run-hooks 'rmail-show-message-hook)
2745 ;; If there is a summary buffer, try to move to this message
2746 ;; in that buffer. But don't complain if this message
2747 ;; is not mentioned in the summary.
2748 ;; Don't do this at all if we were called on behalf
2749 ;; of cursor motion in the summary buffer.
2750 (and (rmail-summary-exists) (not no-summary)
2751 (let ((curr-msg rmail-current-message))
2752 (rmail-select-summary
2753 (rmail-summary-goto-msg curr-msg t t))))
2754 (with-current-buffer rmail-buffer
2755 (rmail-auto-file))
2756 (if blurb
2757 (message blurb))))))
2758
2759 (defun rmail-redecode-body (coding)
2760 "Decode the body of the current message using coding system CODING.
2761 This is useful with mail messages that have malformed or missing
2762 charset= headers.
2763
2764 This function assumes that the current message is already decoded
2765 and displayed in the RMAIL buffer, but the coding system used to
2766 decode it was incorrect. It then encodes the message back to its
2767 original form, and decodes it again, using the coding system CODING.
2768
2769 Note that if Emacs erroneously auto-detected one of the iso-2022
2770 encodings in the message, this function might fail because the escape
2771 sequences that switch between character sets and also single-shift and
2772 locking-shift codes are impossible to recover. This function is meant
2773 to be used to fix messages encoded with 8-bit encodings, such as
2774 iso-8859, koi8-r, etc."
2775 (interactive "zCoding system for re-decoding this message: ")
2776 (when (not rmail-enable-mime)
2777 (or (eq major-mode 'rmail-mode)
2778 (switch-to-buffer rmail-buffer))
2779 (save-excursion
2780 (let ((pruned (rmail-msg-is-pruned)))
2781 (unwind-protect
2782 (let ((msgbeg (rmail-msgbeg rmail-current-message))
2783 (msgend (rmail-msgend rmail-current-message))
2784 x-coding-header)
2785 ;; We need the message headers pruned (we later restore
2786 ;; the pruned stat to what it was, see the end of
2787 ;; unwind-protect form).
2788 (or pruned
2789 (rmail-toggle-header 1))
2790 (narrow-to-region msgbeg msgend)
2791 (goto-char (point-min))
2792 (when (search-forward "\n*** EOOH ***\n" (point-max) t)
2793 (narrow-to-region msgbeg (point)))
2794 (goto-char (point-min))
2795 (if (re-search-forward "^X-Coding-System: *\\(.*\\)$" nil t)
2796 (let ((old-coding (intern (match-string 1)))
2797 (buffer-read-only nil))
2798 (check-coding-system old-coding)
2799 ;; Make sure the new coding system uses the same EOL
2800 ;; conversion, to prevent ^M characters from popping
2801 ;; up all over the place.
2802 (setq coding
2803 (coding-system-change-eol-conversion
2804 coding
2805 (coding-system-eol-type old-coding)))
2806 (setq x-coding-header (point-marker))
2807 (narrow-to-region msgbeg msgend)
2808 (encode-coding-region (point) msgend old-coding)
2809 (decode-coding-region (point) msgend coding)
2810 (setq last-coding-system-used coding)
2811 ;; Rewrite the coding-system header according
2812 ;; to what we did.
2813 (goto-char x-coding-header)
2814 (delete-region (point)
2815 (save-excursion
2816 (beginning-of-line)
2817 (point)))
2818 (insert "X-Coding-System: "
2819 (symbol-name last-coding-system-used))
2820 (set-marker x-coding-header nil)
2821 (rmail-show-message))
2822 (error "No X-Coding-System header found")))
2823 (or pruned
2824 (rmail-toggle-header 0)))))))
2825
2826 ;; Find all occurrences of certain fields, and highlight them.
2827 (defun rmail-highlight-headers ()
2828 ;; Do this only if the system supports faces.
2829 (if (and (fboundp 'internal-find-face)
2830 rmail-highlighted-headers)
2831 (save-excursion
2832 (search-forward "\n\n" nil 'move)
2833 (save-restriction
2834 (narrow-to-region (point-min) (point))
2835 (let ((case-fold-search t)
2836 (inhibit-read-only t)
2837 ;; Highlight with boldface if that is available.
2838 ;; Otherwise use the `highlight' face.
2839 (face (or rmail-highlight-face
2840 (if (face-differs-from-default-p 'bold)
2841 'bold 'highlight)))
2842 ;; List of overlays to reuse.
2843 (overlays rmail-overlay-list))
2844 (goto-char (point-min))
2845 (while (re-search-forward rmail-highlighted-headers nil t)
2846 (skip-chars-forward " \t")
2847 (let ((beg (point))
2848 overlay)
2849 (while (progn (forward-line 1)
2850 (looking-at "[ \t]")))
2851 ;; Back up over newline, then trailing spaces or tabs
2852 (forward-char -1)
2853 (while (member (preceding-char) '(? ?\t))
2854 (forward-char -1))
2855 (if overlays
2856 ;; Reuse an overlay we already have.
2857 (progn
2858 (setq overlay (car overlays)
2859 overlays (cdr overlays))
2860 (overlay-put overlay 'face face)
2861 (move-overlay overlay beg (point)))
2862 ;; Make a new overlay and add it to
2863 ;; rmail-overlay-list.
2864 (setq overlay (make-overlay beg (point)))
2865 (overlay-put overlay 'face face)
2866 (setq rmail-overlay-list
2867 (cons overlay rmail-overlay-list))))))))))
2868
2869 (defun rmail-auto-file ()
2870 "Automatically move a message into a sub-folder based on criteria.
2871 Called when a new message is displayed."
2872 (if (or (rmail-message-labels-p rmail-current-message "filed")
2873 (not (string= (buffer-file-name)
2874 (expand-file-name rmail-file-name))))
2875 ;; Do nothing if it's already been filed.
2876 nil
2877 ;; Find out some basics (common fields)
2878 (let ((from (mail-fetch-field "from"))
2879 (subj (mail-fetch-field "subject"))
2880 (to (concat (mail-fetch-field "to") "," (mail-fetch-field "cc")))
2881 (d rmail-automatic-folder-directives)
2882 (directive-loop nil)
2883 (folder nil))
2884 (while d
2885 (setq folder (car (car d))
2886 directive-loop (cdr (car d)))
2887 (while (and (car directive-loop)
2888 (let ((f (cond
2889 ((string= (car directive-loop) "from") from)
2890 ((string= (car directive-loop) "to") to)
2891 ((string= (car directive-loop) "subject") subj)
2892 (t (mail-fetch-field (car directive-loop))))))
2893 (and f (string-match (car (cdr directive-loop)) f))))
2894 (setq directive-loop (cdr (cdr directive-loop))))
2895 ;; If there are no directives left, then it was a complete match.
2896 (if (null directive-loop)
2897 (if (null folder)
2898 (rmail-delete-forward)
2899 (if (string= "/dev/null" folder)
2900 (rmail-delete-message)
2901 (rmail-output-to-rmail-file folder 1 t)
2902 (setq d nil))))
2903 (setq d (cdr d))))))
2904
2905 (defun rmail-next-message (n)
2906 "Show following message whether deleted or not.
2907 With prefix arg N, moves forward N messages, or backward if N is negative."
2908 (interactive "p")
2909 (set-buffer rmail-buffer)
2910 (rmail-maybe-set-message-counters)
2911 (rmail-show-message (+ rmail-current-message n)))
2912
2913 (defun rmail-previous-message (n)
2914 "Show previous message whether deleted or not.
2915 With prefix arg N, moves backward N messages, or forward if N is negative."
2916 (interactive "p")
2917 (rmail-next-message (- n)))
2918
2919 (defun rmail-next-undeleted-message (n)
2920 "Show following non-deleted message.
2921 With prefix arg N, moves forward N non-deleted messages,
2922 or backward if N is negative.
2923
2924 Returns t if a new message is being shown, nil otherwise."
2925 (interactive "p")
2926 (set-buffer rmail-buffer)
2927 (rmail-maybe-set-message-counters)
2928 (let ((lastwin rmail-current-message)
2929 (current rmail-current-message))
2930 (while (and (> n 0) (< current rmail-total-messages))
2931 (setq current (1+ current))
2932 (if (not (rmail-message-deleted-p current))
2933 (setq lastwin current n (1- n))))
2934 (while (and (< n 0) (> current 1))
2935 (setq current (1- current))
2936 (if (not (rmail-message-deleted-p current))
2937 (setq lastwin current n (1+ n))))
2938 (if (/= lastwin rmail-current-message)
2939 (progn (rmail-show-message lastwin)
2940 t)
2941 (if (< n 0)
2942 (message "No previous nondeleted message"))
2943 (if (> n 0)
2944 (message "No following nondeleted message"))
2945 nil)))
2946
2947 (defun rmail-previous-undeleted-message (n)
2948 "Show previous non-deleted message.
2949 With prefix argument N, moves backward N non-deleted messages,
2950 or forward if N is negative."
2951 (interactive "p")
2952 (rmail-next-undeleted-message (- n)))
2953
2954 (defun rmail-first-message ()
2955 "Show first message in file."
2956 (interactive)
2957 (rmail-maybe-set-message-counters)
2958 (rmail-show-message 1))
2959
2960 (defun rmail-last-message ()
2961 "Show last message in file."
2962 (interactive)
2963 (rmail-maybe-set-message-counters)
2964 (rmail-show-message rmail-total-messages))
2965
2966 (defun rmail-what-message ()
2967 (let ((where (point))
2968 (low 1)
2969 (high rmail-total-messages)
2970 (mid (/ rmail-total-messages 2)))
2971 (while (> (- high low) 1)
2972 (if (>= where (rmail-msgbeg mid))
2973 (setq low mid)
2974 (setq high mid))
2975 (setq mid (+ low (/ (- high low) 2))))
2976 (if (>= where (rmail-msgbeg high)) high low)))
2977
2978 (defun rmail-message-recipients-p (msg recipients &optional primary-only)
2979 (save-restriction
2980 (goto-char (rmail-msgbeg msg))
2981 (search-forward "\n*** EOOH ***\n")
2982 (narrow-to-region (point) (progn (search-forward "\n\n") (point)))
2983 (or (string-match recipients (or (mail-fetch-field "To") ""))
2984 (string-match recipients (or (mail-fetch-field "From") ""))
2985 (if (not primary-only)
2986 (string-match recipients (or (mail-fetch-field "Cc") ""))))))
2987
2988 (defun rmail-message-regexp-p (n regexp)
2989 "Return t, if for message number N, regexp REGEXP matches in the header."
2990 (let ((beg (rmail-msgbeg n))
2991 (end (rmail-msgend n)))
2992 (goto-char beg)
2993 (forward-line 1)
2994 (save-excursion
2995 (save-restriction
2996 (if (prog1 (= (following-char) ?0)
2997 (forward-line 2)
2998 ;; If there's a Summary-line in the (otherwise empty)
2999 ;; header, we didn't yet get past the EOOH line.
3000 (when (looking-at "^\\*\\*\\* EOOH \\*\\*\\*\n")
3001 (forward-line 1))
3002 (setq beg (point))
3003 (narrow-to-region (point) end))
3004 (progn
3005 (rfc822-goto-eoh)
3006 (setq end (point)))
3007 (setq beg (point))
3008 (search-forward "\n*** EOOH ***\n" end t)
3009 (setq end (1+ (match-beginning 0)))))
3010 (goto-char beg)
3011 (if rmail-enable-mime
3012 (funcall rmail-search-mime-header-function n regexp end)
3013 (re-search-forward regexp end t)))))
3014
3015 (defun rmail-search-message (msg regexp)
3016 "Return non-nil, if for message number MSG, regexp REGEXP matches."
3017 (goto-char (rmail-msgbeg msg))
3018 (if rmail-enable-mime
3019 (funcall rmail-search-mime-message-function msg regexp)
3020 (re-search-forward regexp (rmail-msgend msg) t)))
3021
3022 (defvar rmail-search-last-regexp nil)
3023 (defun rmail-search (regexp &optional n)
3024 "Show message containing next match for REGEXP (but not the current msg).
3025 Prefix argument gives repeat count; negative argument means search
3026 backwards (through earlier messages).
3027 Interactively, empty argument means use same regexp used last time."
3028 (interactive
3029 (let* ((reversep (< (prefix-numeric-value current-prefix-arg) 0))
3030 (prompt
3031 (concat (if reversep "Reverse " "") "Rmail search (regexp): "))
3032 regexp)
3033 (if rmail-search-last-regexp
3034 (setq prompt (concat prompt
3035 "(default "
3036 rmail-search-last-regexp
3037 ") ")))
3038 (setq regexp (read-string prompt))
3039 (cond ((not (equal regexp ""))
3040 (setq rmail-search-last-regexp regexp))
3041 ((not rmail-search-last-regexp)
3042 (error "No previous Rmail search string")))
3043 (list rmail-search-last-regexp
3044 (prefix-numeric-value current-prefix-arg))))
3045 (or n (setq n 1))
3046 (message "%sRmail search for %s..."
3047 (if (< n 0) "Reverse " "")
3048 regexp)
3049 (set-buffer rmail-buffer)
3050 (rmail-maybe-set-message-counters)
3051 (let ((omin (point-min))
3052 (omax (point-max))
3053 (opoint (point))
3054 win
3055 (reversep (< n 0))
3056 (msg rmail-current-message))
3057 (unwind-protect
3058 (progn
3059 (widen)
3060 (while (/= n 0)
3061 ;; Check messages one by one, advancing message number up or down
3062 ;; but searching forward through each message.
3063 (if reversep
3064 (while (and (null win) (> msg 1))
3065 (setq msg (1- msg)
3066 win (rmail-search-message msg regexp)))
3067 (while (and (null win) (< msg rmail-total-messages))
3068 (setq msg (1+ msg)
3069 win (rmail-search-message msg regexp))))
3070 (setq n (+ n (if reversep 1 -1)))))
3071 (if win
3072 (progn
3073 (rmail-show-message msg)
3074 ;; Search forward (if this is a normal search) or backward
3075 ;; (if this is a reverse search) through this message to
3076 ;; position point. This search may fail because REGEXP
3077 ;; was found in the hidden portion of this message. In
3078 ;; that case, move point to the beginning of visible
3079 ;; portion.
3080 (if reversep
3081 (progn
3082 (goto-char (point-max))
3083 (re-search-backward regexp nil 'move))
3084 (goto-char (point-min))
3085 (re-search-forward regexp nil t))
3086 (message "%sRmail search for %s...done"
3087 (if reversep "Reverse " "")
3088 regexp))
3089 (goto-char opoint)
3090 (narrow-to-region omin omax)
3091 (ding)
3092 (message "Search failed: %s" regexp)))))
3093
3094 (defun rmail-search-backwards (regexp &optional n)
3095 "Show message containing previous match for REGEXP.
3096 Prefix argument gives repeat count; negative argument means search
3097 forward (through later messages).
3098 Interactively, empty argument means use same regexp used last time."
3099 (interactive
3100 (let* ((reversep (>= (prefix-numeric-value current-prefix-arg) 0))
3101 (prompt
3102 (concat (if reversep "Reverse " "") "Rmail search (regexp): "))
3103 regexp)
3104 (if rmail-search-last-regexp
3105 (setq prompt (concat prompt
3106 "(default "
3107 rmail-search-last-regexp
3108 ") ")))
3109 (setq regexp (read-string prompt))
3110 (cond ((not (equal regexp ""))
3111 (setq rmail-search-last-regexp regexp))
3112 ((not rmail-search-last-regexp)
3113 (error "No previous Rmail search string")))
3114 (list rmail-search-last-regexp
3115 (prefix-numeric-value current-prefix-arg))))
3116 (rmail-search regexp (- (or n 1))))
3117
3118 ;; Show the first message which has the `unseen' attribute.
3119 (defun rmail-first-unseen-message ()
3120 (rmail-maybe-set-message-counters)
3121 (let ((current 1)
3122 found)
3123 (save-restriction
3124 (widen)
3125 (while (and (not found) (<= current rmail-total-messages))
3126 (if (rmail-message-labels-p current ", ?\\(unseen\\),")
3127 (setq found current))
3128 (setq current (1+ current))))
3129 ;; Let the caller show the message.
3130 ;; (if found
3131 ;; (rmail-show-message found))
3132 found))
3133
3134 (defun rmail-next-same-subject (n)
3135 "Go to the next mail message having the same subject header.
3136 With prefix argument N, do this N times.
3137 If N is negative, go backwards instead."
3138 (interactive "p")
3139 (let ((subject (mail-fetch-field "Subject"))
3140 (forward (> n 0))
3141 (i rmail-current-message)
3142 (case-fold-search t)
3143 search-regexp found)
3144 (if (string-match "\\`[ \t]+" subject)
3145 (setq subject (substring subject (match-end 0))))
3146 (if (string-match "Re:[ \t]*" subject)
3147 (setq subject (substring subject (match-end 0))))
3148 (if (string-match "[ \t]+\\'" subject)
3149 (setq subject (substring subject 0 (match-beginning 0))))
3150 (setq search-regexp (concat "^Subject: *\\(Re:[ \t]*\\)?"
3151 (regexp-quote subject)
3152 "[ \t]*\n"))
3153 (save-excursion
3154 (save-restriction
3155 (widen)
3156 (while (and (/= n 0)
3157 (if forward
3158 (< i rmail-total-messages)
3159 (> i 1)))
3160 (let (done)
3161 (while (and (not done)
3162 (if forward
3163 (< i rmail-total-messages)
3164 (> i 1)))
3165 (setq i (if forward (1+ i) (1- i)))
3166 (goto-char (rmail-msgbeg i))
3167 (search-forward "\n*** EOOH ***\n")
3168 (let ((beg (point)) end)
3169 (search-forward "\n\n")
3170 (setq end (point))
3171 (goto-char beg)
3172 (setq done (re-search-forward search-regexp end t))))
3173 (if done (setq found i)))
3174 (setq n (if forward (1- n) (1+ n))))))
3175 (if found
3176 (rmail-show-message found)
3177 (error "No %s message with same subject"
3178 (if forward "following" "previous")))))
3179
3180 (defun rmail-previous-same-subject (n)
3181 "Go to the previous mail message having the same subject header.
3182 With prefix argument N, do this N times.
3183 If N is negative, go forwards instead."
3184 (interactive "p")
3185 (rmail-next-same-subject (- n)))
3186 \f
3187 ;;;; *** Rmail Message Deletion Commands ***
3188
3189 (defun rmail-message-deleted-p (n)
3190 (= (aref rmail-deleted-vector n) ?D))
3191
3192 (defun rmail-set-message-deleted-p (n state)
3193 (aset rmail-deleted-vector n (if state ?D ?\ )))
3194
3195 (defun rmail-delete-message ()
3196 "Delete this message and stay on it."
3197 (interactive)
3198 (rmail-set-attribute "deleted" t)
3199 (run-hooks 'rmail-delete-message-hook))
3200
3201 (defun rmail-undelete-previous-message ()
3202 "Back up to deleted message, select it, and undelete it."
3203 (interactive)
3204 (set-buffer rmail-buffer)
3205 (let ((msg rmail-current-message))
3206 (while (and (> msg 0)
3207 (not (rmail-message-deleted-p msg)))
3208 (setq msg (1- msg)))
3209 (if (= msg 0)
3210 (error "No previous deleted message")
3211 (if (/= msg rmail-current-message)
3212 (rmail-show-message msg))
3213 (rmail-set-attribute "deleted" nil)
3214 (if (rmail-summary-exists)
3215 (save-excursion
3216 (set-buffer rmail-summary-buffer)
3217 (rmail-summary-mark-undeleted msg)))
3218 (rmail-maybe-display-summary))))
3219
3220 (defun rmail-delete-forward (&optional backward)
3221 "Delete this message and move to next nondeleted one.
3222 Deleted messages stay in the file until the \\[rmail-expunge] command is given.
3223 With prefix argument, delete and move backward.
3224
3225 Returns t if a new message is displayed after the delete, or nil otherwise."
3226 (interactive "P")
3227 (rmail-set-attribute "deleted" t)
3228 (run-hooks 'rmail-delete-message-hook)
3229 (let ((del-msg rmail-current-message))
3230 (if (rmail-summary-exists)
3231 (rmail-select-summary
3232 (rmail-summary-mark-deleted del-msg)))
3233 (prog1 (rmail-next-undeleted-message (if backward -1 1))
3234 (rmail-maybe-display-summary))))
3235
3236 (defun rmail-delete-backward ()
3237 "Delete this message and move to previous nondeleted one.
3238 Deleted messages stay in the file until the \\[rmail-expunge] command is given."
3239 (interactive)
3240 (rmail-delete-forward t))
3241
3242 ;; Compute the message number a given message would have after expunging.
3243 ;; The present number of the message is OLDNUM.
3244 ;; DELETEDVEC should be rmail-deleted-vector.
3245 ;; The value is nil for a message that would be deleted.
3246 (defun rmail-msg-number-after-expunge (deletedvec oldnum)
3247 (if (or (null oldnum) (= (aref deletedvec oldnum) ?D))
3248 nil
3249 (let ((i 0)
3250 (newnum 0))
3251 (while (< i oldnum)
3252 (if (/= (aref deletedvec i) ?D)
3253 (setq newnum (1+ newnum)))
3254 (setq i (1+ i)))
3255 newnum)))
3256
3257 (defun rmail-expunge-confirmed ()
3258 "Return t if deleted message should be expunged. If necessary, ask the user.
3259 See also user-option `rmail-confirm-expunge'."
3260 (set-buffer rmail-buffer)
3261 (or (not (stringp rmail-deleted-vector))
3262 (not (string-match "D" rmail-deleted-vector))
3263 (null rmail-confirm-expunge)
3264 (funcall rmail-confirm-expunge
3265 "Erase deleted messages from Rmail file? ")))
3266
3267 (defun rmail-only-expunge (&optional dont-show)
3268 "Actually erase all deleted messages in the file."
3269 (interactive)
3270 (set-buffer rmail-buffer)
3271 (message "Expunging deleted messages...")
3272 ;; Discard all undo records for this buffer.
3273 (or (eq buffer-undo-list t)
3274 (setq buffer-undo-list nil))
3275 (rmail-maybe-set-message-counters)
3276 (let* ((omax (- (buffer-size) (point-max)))
3277 (omin (- (buffer-size) (point-min)))
3278 (opoint (if (and (> rmail-current-message 0)
3279 (rmail-message-deleted-p rmail-current-message))
3280 0
3281 (if rmail-enable-mime
3282 (with-current-buffer rmail-view-buffer
3283 (- (point)(point-min)))
3284 (- (point) (point-min)))))
3285 (messages-head (cons (aref rmail-message-vector 0) nil))
3286 (messages-tail messages-head)
3287 ;; Don't make any undo records for the expunging.
3288 (buffer-undo-list t)
3289 (win))
3290 (unwind-protect
3291 (save-excursion
3292 (widen)
3293 (goto-char (point-min))
3294 (let ((counter 0)
3295 (number 1)
3296 (total rmail-total-messages)
3297 (new-message-number rmail-current-message)
3298 (new-summary nil)
3299 (new-msgref (list (list 0)))
3300 (rmailbuf (current-buffer))
3301 (buffer-read-only nil)
3302 (messages rmail-message-vector)
3303 (deleted rmail-deleted-vector)
3304 (summary rmail-summary-vector))
3305 (setq rmail-total-messages nil
3306 rmail-current-message nil
3307 rmail-message-vector nil
3308 rmail-deleted-vector nil
3309 rmail-summary-vector nil)
3310
3311 (while (<= number total)
3312 (if (= (aref deleted number) ?D)
3313 (progn
3314 (delete-region
3315 (marker-position (aref messages number))
3316 (marker-position (aref messages (1+ number))))
3317 (move-marker (aref messages number) nil)
3318 (if (> new-message-number counter)
3319 (setq new-message-number (1- new-message-number))))
3320 (setq counter (1+ counter))
3321 (setq messages-tail
3322 (setcdr messages-tail
3323 (cons (aref messages number) nil)))
3324 (setq new-summary
3325 (cons (if (= counter number) (aref summary (1- number)))
3326 new-summary))
3327 (setq new-msgref
3328 (cons (aref rmail-msgref-vector number)
3329 new-msgref))
3330 (setcar (car new-msgref) counter))
3331 (if (zerop (% (setq number (1+ number)) 20))
3332 (message "Expunging deleted messages...%d" number)))
3333 (setq messages-tail
3334 (setcdr messages-tail
3335 (cons (aref messages number) nil)))
3336 (setq rmail-current-message new-message-number
3337 rmail-total-messages counter
3338 rmail-message-vector (apply 'vector messages-head)
3339 rmail-deleted-vector (make-string (1+ counter) ?\ )
3340 rmail-summary-vector (vconcat (nreverse new-summary))
3341 rmail-msgref-vector (apply 'vector (nreverse new-msgref))
3342 win t)))
3343 (message "Expunging deleted messages...done")
3344 (if (not win)
3345 (narrow-to-region (- (buffer-size) omin) (- (buffer-size) omax)))
3346 (if (not dont-show)
3347 (rmail-show-message
3348 (if (zerop rmail-current-message) 1 nil)))
3349 (if rmail-enable-mime
3350 (goto-char (+ (point-min) opoint))
3351 (goto-char (+ (point) opoint))))))
3352
3353 (defun rmail-expunge ()
3354 "Erase deleted messages from Rmail file and summary buffer."
3355 (interactive)
3356 (when (rmail-expunge-confirmed)
3357 (rmail-only-expunge)
3358 (if (rmail-summary-exists)
3359 (rmail-select-summary (rmail-update-summary)))))
3360 \f
3361 ;;;; *** Rmail Mailing Commands ***
3362
3363 (defun rmail-start-mail (&optional noerase to subject in-reply-to cc
3364 replybuffer sendactions same-window others)
3365 (let (yank-action)
3366 (if replybuffer
3367 (setq yank-action (list 'insert-buffer replybuffer)))
3368 (setq others (cons (cons "cc" cc) others))
3369 (setq others (cons (cons "in-reply-to" in-reply-to) others))
3370 (if same-window
3371 (compose-mail to subject others
3372 noerase nil
3373 yank-action sendactions)
3374 (if rmail-mail-new-frame
3375 (prog1
3376 (compose-mail to subject others
3377 noerase 'switch-to-buffer-other-frame
3378 yank-action sendactions)
3379 ;; This is not a standard frame parameter;
3380 ;; nothing except sendmail.el looks at it.
3381 (modify-frame-parameters (selected-frame)
3382 '((mail-dedicated-frame . t))))
3383 (compose-mail to subject others
3384 noerase 'switch-to-buffer-other-window
3385 yank-action sendactions)))))
3386
3387 (defun rmail-mail ()
3388 "Send mail in another window.
3389 While composing the message, use \\[mail-yank-original] to yank the
3390 original message into it."
3391 (interactive)
3392 (rmail-start-mail nil nil nil nil nil rmail-view-buffer))
3393
3394 (defun rmail-continue ()
3395 "Continue composing outgoing message previously being composed."
3396 (interactive)
3397 (rmail-start-mail t))
3398
3399 (defun rmail-reply (just-sender)
3400 "Reply to the current message.
3401 Normally include CC: to all other recipients of original message;
3402 prefix argument means ignore them. While composing the reply,
3403 use \\[mail-yank-original] to yank the original message into it."
3404 (interactive "P")
3405 (let (from reply-to cc subject date to message-id references
3406 resent-to resent-cc resent-reply-to
3407 (msgnum rmail-current-message))
3408 (save-excursion
3409 (save-restriction
3410 (if rmail-enable-mime
3411 (narrow-to-region
3412 (goto-char (point-min))
3413 (if (search-forward "\n\n" nil 'move)
3414 (1+ (match-beginning 0))
3415 (point)))
3416 (widen)
3417 (goto-char (rmail-msgbeg rmail-current-message))
3418 (forward-line 1)
3419 (if (= (following-char) ?0)
3420 (narrow-to-region
3421 (progn (forward-line 2)
3422 (point))
3423 (progn (search-forward "\n\n" (rmail-msgend rmail-current-message)
3424 'move)
3425 (point)))
3426 (narrow-to-region (point)
3427 (progn (search-forward "\n*** EOOH ***\n")
3428 (beginning-of-line) (point)))))
3429 (setq from (mail-fetch-field "from")
3430 reply-to (or (mail-fetch-field "mail-reply-to" nil t)
3431 (mail-fetch-field "reply-to" nil t)
3432 from)
3433 subject (mail-fetch-field "subject")
3434 date (mail-fetch-field "date")
3435 message-id (mail-fetch-field "message-id")
3436 references (mail-fetch-field "references" nil nil t)
3437 resent-reply-to (mail-fetch-field "resent-reply-to" nil t)
3438 resent-cc (and (not just-sender)
3439 (mail-fetch-field "resent-cc" nil t))
3440 resent-to (or (mail-fetch-field "resent-to" nil t) "")
3441 ;;; resent-subject (mail-fetch-field "resent-subject")
3442 ;;; resent-date (mail-fetch-field "resent-date")
3443 ;;; resent-message-id (mail-fetch-field "resent-message-id")
3444 )
3445 (unless just-sender
3446 (if (mail-fetch-field "mail-followup-to" nil t)
3447 ;; If this header field is present, use it instead of the To and CC fields.
3448 (setq to (mail-fetch-field "mail-followup-to" nil t))
3449 (setq cc (or (mail-fetch-field "cc" nil t) "")
3450 to (or (mail-fetch-field "to" nil t) ""))))
3451
3452 ))
3453
3454 ;; Merge the resent-to and resent-cc into the to and cc.
3455 (if (and resent-to (not (equal resent-to "")))
3456 (if (not (equal to ""))
3457 (setq to (concat to ", " resent-to))
3458 (setq to resent-to)))
3459 (if (and resent-cc (not (equal resent-cc "")))
3460 (if (not (equal cc ""))
3461 (setq cc (concat cc ", " resent-cc))
3462 (setq cc resent-cc)))
3463 ;; Add `Re: ' to subject if not there already.
3464 (and (stringp subject)
3465 (setq subject
3466 (concat rmail-reply-prefix
3467 (if (let ((case-fold-search t))
3468 (string-match rmail-reply-regexp subject))
3469 (substring subject (match-end 0))
3470 subject))))
3471 (rmail-start-mail
3472 nil
3473 ;; Using mail-strip-quoted-names is undesirable with newer mailers
3474 ;; since they can handle the names unstripped.
3475 ;; I don't know whether there are other mailers that still
3476 ;; need the names to be stripped.
3477 ;;; (mail-strip-quoted-names reply-to)
3478 ;; Remove unwanted names from reply-to, since Mail-Followup-To
3479 ;; header causes all the names in it to wind up in reply-to, not
3480 ;; in cc. But if what's left is an empty list, use the original.
3481 (let* ((reply-to-list (rmail-dont-reply-to reply-to)))
3482 (if (string= reply-to-list "") reply-to reply-to-list))
3483 subject
3484 (rmail-make-in-reply-to-field from date message-id)
3485 (if just-sender
3486 nil
3487 ;; mail-strip-quoted-names is NOT necessary for rmail-dont-reply-to
3488 ;; to do its job.
3489 (let* ((cc-list (rmail-dont-reply-to
3490 (mail-strip-quoted-names
3491 (if (null cc) to (concat to ", " cc))))))
3492 (if (string= cc-list "") nil cc-list)))
3493 rmail-view-buffer
3494 (list (list 'rmail-mark-message
3495 rmail-buffer
3496 (with-current-buffer rmail-buffer
3497 (aref rmail-msgref-vector msgnum))
3498 "answered"))
3499 nil
3500 (list (cons "References" (concat (mapconcat 'identity references " ")
3501 " " message-id))))))
3502
3503 (defun rmail-mark-message (buffer msgnum-list attribute)
3504 "Give BUFFER's message number in MSGNUM-LIST the attribute ATTRIBUTE.
3505 This is use in the send-actions for message buffers.
3506 MSGNUM-LIST is a list of the form (MSGNUM)
3507 which is an element of rmail-msgref-vector."
3508 (save-excursion
3509 (set-buffer buffer)
3510 (if (car msgnum-list)
3511 (rmail-set-attribute attribute t (car msgnum-list)))))
3512
3513 (defun rmail-make-in-reply-to-field (from date message-id)
3514 (cond ((not from)
3515 (if message-id
3516 message-id
3517 nil))
3518 (mail-use-rfc822
3519 (require 'rfc822)
3520 (let ((tem (car (rfc822-addresses from))))
3521 (if message-id
3522 (if (or (not tem)
3523 (string-match
3524 (regexp-quote (if (string-match "@[^@]*\\'" tem)
3525 (substring tem 0
3526 (match-beginning 0))
3527 tem))
3528 message-id))
3529 ;; missing From, or Message-ID is sufficiently informative
3530 message-id
3531 (concat message-id " (" tem ")"))
3532 ;; Copy TEM, discarding text properties.
3533 (setq tem (copy-sequence tem))
3534 (set-text-properties 0 (length tem) nil tem)
3535 (setq tem (copy-sequence tem))
3536 ;; Use prin1 to fake RFC822 quoting
3537 (let ((field (prin1-to-string tem)))
3538 (if date
3539 (concat field "'s message of " date)
3540 field)))))
3541 ((let* ((foo "[^][\000-\037()<>@,;:\\\" ]+")
3542 (bar "[^][\000-\037()<>@,;:\\\"]+"))
3543 ;; These strings both match all non-ASCII characters.
3544 (or (string-match (concat "\\`[ \t]*\\(" bar
3545 "\\)\\(<" foo "@" foo ">\\)?[ \t]*\\'")
3546 ;; "Unix Loser <Foo@bar.edu>" => "Unix Loser"
3547 from)
3548 (string-match (concat "\\`[ \t]*<" foo "@" foo ">[ \t]*(\\("
3549 bar "\\))[ \t]*\\'")
3550 ;; "<Bugs@bar.edu>" (Losing Unix) => "Losing Unix"
3551 from)))
3552 (let ((start (match-beginning 1))
3553 (end (match-end 1)))
3554 ;; Trim whitespace which above regexp match allows
3555 (while (and (< start end)
3556 (memq (aref from start) '(?\t ?\ )))
3557 (setq start (1+ start)))
3558 (while (and (< start end)
3559 (memq (aref from (1- end)) '(?\t ?\ )))
3560 (setq end (1- end)))
3561 (let ((field (substring from start end)))
3562 (if date (setq field (concat "message from " field " on " date)))
3563 (if message-id
3564 ;; "<AA259@bar.edu> (message from Unix Loser on 1-Apr-89)"
3565 (concat message-id " (" field ")")
3566 field))))
3567 (t
3568 ;; If we can't kludge it simply, do it correctly
3569 (let ((mail-use-rfc822 t))
3570 (rmail-make-in-reply-to-field from date message-id)))))
3571 \f
3572 (defun rmail-forward (resend)
3573 "Forward the current message to another user.
3574 With prefix argument, \"resend\" the message instead of forwarding it;
3575 see the documentation of `rmail-resend'."
3576 (interactive "P")
3577 (if resend
3578 (call-interactively 'rmail-resend)
3579 (let ((forward-buffer rmail-buffer)
3580 (msgnum rmail-current-message)
3581 (subject (concat "["
3582 (let ((from (or (mail-fetch-field "From")
3583 (mail-fetch-field ">From"))))
3584 (if from
3585 (concat (mail-strip-quoted-names from) ": ")
3586 ""))
3587 (or (mail-fetch-field "Subject") "")
3588 "]")))
3589 (if (rmail-start-mail
3590 nil nil subject nil nil nil
3591 (list (list 'rmail-mark-message
3592 forward-buffer
3593 (with-current-buffer rmail-buffer
3594 (aref rmail-msgref-vector msgnum))
3595 "forwarded"))
3596 ;; If only one window, use it for the mail buffer.
3597 ;; Otherwise, use another window for the mail buffer
3598 ;; so that the Rmail buffer remains visible
3599 ;; and sending the mail will get back to it.
3600 (and (not rmail-mail-new-frame) (one-window-p t)))
3601 ;; The mail buffer is now current.
3602 (save-excursion
3603 ;; Insert after header separator--before signature if any.
3604 (goto-char (mail-text-start))
3605 (if (or rmail-enable-mime rmail-enable-mime-composing)
3606 (funcall rmail-insert-mime-forwarded-message-function
3607 forward-buffer)
3608 (insert "------- Start of forwarded message -------\n")
3609 ;; Quote lines with `- ' if they start with `-'.
3610 (let ((beg (point)) end)
3611 (setq end (point-marker))
3612 (set-marker-insertion-type end t)
3613 (insert-buffer-substring forward-buffer)
3614 (goto-char beg)
3615 (while (re-search-forward "^-" end t)
3616 (beginning-of-line)
3617 (insert "- ")
3618 (forward-line 1))
3619 (goto-char end)
3620 (skip-chars-backward "\n")
3621 (if (< (point) end)
3622 (forward-char 1))
3623 (delete-region (point) end)
3624 (set-marker end nil))
3625 (insert "------- End of forwarded message -------\n"))
3626 (push-mark))))))
3627 \f
3628 (defun rmail-resend (address &optional from comment mail-alias-file)
3629 "Resend current message to ADDRESSES.
3630 ADDRESSES should be a single address, a string consisting of several
3631 addresses separated by commas, or a list of addresses.
3632
3633 Optional FROM is the address to resend the message from, and
3634 defaults from the value of `user-mail-address'.
3635 Optional COMMENT is a string to insert as a comment in the resent message.
3636 Optional ALIAS-FILE is alternate aliases file to be used by sendmail,
3637 typically for purposes of moderating a list."
3638 (interactive "sResend to: ")
3639 (require 'sendmail)
3640 (require 'mailalias)
3641 (unless (or (eq rmail-view-buffer (current-buffer))
3642 (eq rmail-buffer (current-buffer)))
3643 (error "Not an Rmail buffer"))
3644 (if (not from) (setq from user-mail-address))
3645 (let ((tembuf (generate-new-buffer " sendmail temp"))
3646 (case-fold-search nil)
3647 (mail-personal-alias-file
3648 (or mail-alias-file mail-personal-alias-file))
3649 (mailbuf rmail-buffer))
3650 (unwind-protect
3651 (with-current-buffer tembuf
3652 ;;>> Copy message into temp buffer
3653 (if rmail-enable-mime
3654 (funcall rmail-insert-mime-resent-message-function mailbuf)
3655 (insert-buffer-substring mailbuf))
3656 (goto-char (point-min))
3657 ;; Delete any Sender field, since that's not specifiable.
3658 ; Only delete Sender fields in the actual header.
3659 (re-search-forward "^$" nil 'move)
3660 ; Using "while" here rather than "if" because some buggy mail
3661 ; software may have inserted multiple Sender fields.
3662 (while (re-search-backward "^Sender:" nil t)
3663 (let (beg)
3664 (setq beg (point))
3665 (forward-line 1)
3666 (while (looking-at "[ \t]")
3667 (forward-line 1))
3668 (delete-region beg (point))))
3669 ; Go back to the beginning of the buffer so the Resent- fields
3670 ; are inserted there.
3671 (goto-char (point-min))
3672 ;;>> Insert resent-from:
3673 (insert "Resent-From: " from "\n")
3674 (insert "Resent-Date: " (mail-rfc822-date) "\n")
3675 ;;>> Insert resent-to: and bcc if need be.
3676 (let ((before (point)))
3677 (if mail-self-blind
3678 (insert "Resent-Bcc: " (user-login-name) "\n"))
3679 (insert "Resent-To: " (if (stringp address)
3680 address
3681 (mapconcat 'identity address ",\n\t"))
3682 "\n")
3683 ;; Expand abbrevs in the recipients.
3684 (save-excursion
3685 (if (featurep 'mailabbrev)
3686 (let ((end (point-marker))
3687 (local-abbrev-table mail-abbrevs)
3688 (old-syntax-table (syntax-table)))
3689 (if (and (not (vectorp mail-abbrevs))
3690 (file-exists-p mail-personal-alias-file))
3691 (build-mail-abbrevs))
3692 (unless mail-abbrev-syntax-table
3693 (mail-abbrev-make-syntax-table))
3694 (set-syntax-table mail-abbrev-syntax-table)
3695 (goto-char before)
3696 (while (and (< (point) end)
3697 (progn (forward-word 1)
3698 (<= (point) end)))
3699 (expand-abbrev))
3700 (set-syntax-table old-syntax-table))
3701 (expand-mail-aliases before (point)))))
3702 ;;>> Set up comment, if any.
3703 (if (and (sequencep comment) (not (zerop (length comment))))
3704 (let ((before (point))
3705 after)
3706 (insert comment)
3707 (or (eolp) (insert "\n"))
3708 (setq after (point))
3709 (goto-char before)
3710 (while (< (point) after)
3711 (insert "Resent-Comment: ")
3712 (forward-line 1))))
3713 ;; Don't expand aliases in the destination fields
3714 ;; of the original message.
3715 (let (mail-aliases)
3716 (funcall send-mail-function)))
3717 (kill-buffer tembuf))
3718 (with-current-buffer rmail-buffer
3719 (rmail-set-attribute "resent" t rmail-current-message))))
3720 \f
3721 (defvar mail-unsent-separator
3722 (concat "^ *---+ +Unsent message follows +---+ *$\\|"
3723 "^ *---+ +Returned message +---+ *$\\|"
3724 "^ *---+ *Returned mail follows *---+ *$\\|"
3725 "^Start of returned message$\\|"
3726 "^---+ Below this line is a copy of the message.$\\|"
3727 "^ *---+ +Original message +---+ *$\\|"
3728 "^ *--+ +begin message +--+ *$\\|"
3729 "^ *---+ +Original message follows +---+ *$\\|"
3730 "^ *---+ +Your message follows +---+ *$\\|"
3731 "^|? *---+ +Message text follows: +---+ *|?$\\|"
3732 "^ *---+ +This is a copy of \\w+ message, including all the headers.*---+ *$")
3733 "A regexp that matches the separator before the text of a failed message.")
3734
3735 (defvar mail-mime-unsent-header "^Content-Type: message/rfc822 *$"
3736 "A regexp that matches the header of a MIME body part with a failed message.")
3737
3738 (defun rmail-retry-failure ()
3739 "Edit a mail message which is based on the contents of the current message.
3740 For a message rejected by the mail system, extract the interesting headers and
3741 the body of the original message.
3742 If the failed message is a MIME multipart message, it is searched for a
3743 body part with a header which matches the variable `mail-mime-unsent-header'.
3744 Otherwise, the variable `mail-unsent-separator' should match the string that
3745 delimits the returned original message.
3746 The variable `rmail-retry-ignored-headers' is a regular expression
3747 specifying headers which should not be copied into the new message."
3748 (interactive)
3749 (require 'mail-utils)
3750 (let ((rmail-this-buffer (current-buffer))
3751 (msgnum rmail-current-message)
3752 bounce-start bounce-end bounce-indent resending
3753 ;; Fetch any content-type header in current message
3754 ;; Must search thru the whole unpruned header.
3755 (content-type
3756 (save-excursion
3757 (save-restriction
3758 (rmail-narrow-to-non-pruned-header)
3759 (mail-fetch-field "Content-Type") ))))
3760 (save-excursion
3761 (goto-char (point-min))
3762 (let ((case-fold-search t))
3763 (if (and content-type
3764 (string-match
3765 ";[\n\t ]*boundary=\"?\\([-0-9a-z'()+_,./:=? ]+\\)\"?"
3766 content-type))
3767 ;; Handle a MIME multipart bounce message.
3768 (let ((codestring
3769 (concat "\n--"
3770 (substring content-type (match-beginning 1)
3771 (match-end 1)))))
3772 (unless (re-search-forward mail-mime-unsent-header nil t)
3773 (error "Cannot find beginning of header in failed message"))
3774 (unless (search-forward "\n\n" nil t)
3775 (error "Cannot find start of Mime data in failed message"))
3776 (setq bounce-start (point))
3777 (if (search-forward codestring nil t)
3778 (setq bounce-end (match-beginning 0))
3779 (setq bounce-end (point-max))))
3780 ;; Non-MIME bounce.
3781 (or (re-search-forward mail-unsent-separator nil t)
3782 (error "Cannot parse this as a failure message"))
3783 (skip-chars-forward "\n")
3784 ;; Support a style of failure message in which the original
3785 ;; message is indented, and included within lines saying
3786 ;; `Start of returned message' and `End of returned message'.
3787 (if (looking-at " +Received:")
3788 (progn
3789 (setq bounce-start (point))
3790 (skip-chars-forward " ")
3791 (setq bounce-indent (- (current-column)))
3792 (goto-char (point-max))
3793 (re-search-backward "^End of returned message$" nil t)
3794 (setq bounce-end (point)))
3795 ;; One message contained a few random lines before
3796 ;; the old message header. The first line of the
3797 ;; message started with two hyphens. A blank line
3798 ;; followed these random lines. The same line
3799 ;; beginning with two hyphens was possibly marking
3800 ;; the end of the message.
3801 (if (looking-at "^--")
3802 (let ((boundary (buffer-substring-no-properties
3803 (point)
3804 (progn (end-of-line) (point)))))
3805 (search-forward "\n\n")
3806 (skip-chars-forward "\n")
3807 (setq bounce-start (point))
3808 (goto-char (point-max))
3809 (search-backward (concat "\n\n" boundary) bounce-start t)
3810 (setq bounce-end (point)))
3811 (setq bounce-start (point)
3812 bounce-end (point-max)))
3813 (unless (search-forward "\n\n" nil t)
3814 (error "Cannot find end of header in failed message"))))))
3815 ;; We have found the message that bounced, within the current message.
3816 ;; Now start sending new message; default header fields from original.
3817 ;; Turn off the usual actions for initializing the message body
3818 ;; because we want to get only the text from the failure message.
3819 (let (mail-signature mail-setup-hook)
3820 (if (rmail-start-mail nil nil nil nil nil rmail-this-buffer
3821 (list (list 'rmail-mark-message
3822 rmail-this-buffer
3823 (aref rmail-msgref-vector msgnum)
3824 "retried")))
3825 ;; Insert original text as initial text of new draft message.
3826 ;; Bind inhibit-read-only since the header delimiter
3827 ;; of the previous message was probably read-only.
3828 (let ((inhibit-read-only t)
3829 rmail-displayed-headers
3830 rmail-ignored-headers)
3831 (erase-buffer)
3832 (insert-buffer-substring rmail-this-buffer
3833 bounce-start bounce-end)
3834 (goto-char (point-min))
3835 (if bounce-indent
3836 (indent-rigidly (point-min) (point-max) bounce-indent))
3837 (rmail-clear-headers rmail-retry-ignored-headers)
3838 (rmail-clear-headers "^sender:\\|^return-path:\\|^received:")
3839 (mail-sendmail-delimit-header)
3840 (save-restriction
3841 (narrow-to-region (point-min) (mail-header-end))
3842 (setq resending (mail-fetch-field "resent-to"))
3843 (if mail-self-blind
3844 (if resending
3845 (insert "Resent-Bcc: " (user-login-name) "\n")
3846 (insert "BCC: " (user-login-name) "\n"))))
3847 (goto-char (point-min))
3848 (mail-position-on-field (if resending "Resent-To" "To") t))))))
3849 \f
3850 (defun rmail-summary-exists ()
3851 "Non-nil iff in an RMAIL buffer and an associated summary buffer exists.
3852 In fact, the non-nil value returned is the summary buffer itself."
3853 (and rmail-summary-buffer (buffer-name rmail-summary-buffer)
3854 rmail-summary-buffer))
3855
3856 (defun rmail-summary-displayed ()
3857 "t iff in RMAIL buffer and an associated summary buffer is displayed."
3858 (and rmail-summary-buffer (get-buffer-window rmail-summary-buffer)))
3859
3860 (defcustom rmail-redisplay-summary nil
3861 "*Non-nil means Rmail should show the summary when it changes.
3862 This has an effect only if a summary buffer exists."
3863 :type 'boolean
3864 :group 'rmail-summary)
3865
3866 (defcustom rmail-summary-window-size nil
3867 "*Non-nil means specify the height for an Rmail summary window."
3868 :type '(choice (const :tag "Disabled" nil) integer)
3869 :group 'rmail-summary)
3870
3871 ;; Put the summary buffer back on the screen, if user wants that.
3872 (defun rmail-maybe-display-summary ()
3873 (let ((selected (selected-window))
3874 window)
3875 ;; If requested, make sure the summary is displayed.
3876 (and rmail-summary-buffer (buffer-name rmail-summary-buffer)
3877 rmail-redisplay-summary
3878 (if (get-buffer-window rmail-summary-buffer 0)
3879 ;; It's already in some frame; show that one.
3880 (let ((frame (window-frame
3881 (get-buffer-window rmail-summary-buffer 0))))
3882 (make-frame-visible frame)
3883 (raise-frame frame))
3884 (display-buffer rmail-summary-buffer)))
3885 ;; If requested, set the height of the summary window.
3886 (and rmail-summary-buffer (buffer-name rmail-summary-buffer)
3887 rmail-summary-window-size
3888 (setq window (get-buffer-window rmail-summary-buffer))
3889 ;; Don't try to change the size if just one window in frame.
3890 (not (eq window (frame-root-window (window-frame window))))
3891 (unwind-protect
3892 (progn
3893 (select-window window)
3894 (enlarge-window (- rmail-summary-window-size (window-height))))
3895 (select-window selected)))))
3896 \f
3897 ;;;; *** Rmail Local Fontification ***
3898
3899 (defun rmail-fontify-buffer-function ()
3900 ;; This function's symbol is bound to font-lock-fontify-buffer-function.
3901 (add-hook 'rmail-show-message-hook 'rmail-fontify-message nil t)
3902 ;; If we're already showing a message, fontify it now.
3903 (if rmail-current-message (rmail-fontify-message))
3904 ;; Prevent Font Lock mode from kicking in.
3905 (setq font-lock-fontified t))
3906
3907 (defun rmail-unfontify-buffer-function ()
3908 ;; This function's symbol is bound to font-lock-fontify-unbuffer-function.
3909 (let ((modified (buffer-modified-p))
3910 (buffer-undo-list t) (inhibit-read-only t)
3911 before-change-functions after-change-functions
3912 buffer-file-name buffer-file-truename)
3913 (save-restriction
3914 (widen)
3915 (remove-hook 'rmail-show-message-hook 'rmail-fontify-message t)
3916 (remove-text-properties (point-min) (point-max) '(rmail-fontified nil))
3917 (font-lock-default-unfontify-buffer)
3918 (and (not modified) (buffer-modified-p) (set-buffer-modified-p nil)))))
3919
3920 (defun rmail-fontify-message ()
3921 ;; Fontify the current message if it is not already fontified.
3922 (if (text-property-any (point-min) (point-max) 'rmail-fontified nil)
3923 (let ((modified (buffer-modified-p))
3924 (buffer-undo-list t) (inhibit-read-only t)
3925 before-change-functions after-change-functions
3926 buffer-file-name buffer-file-truename)
3927 (save-excursion
3928 (save-match-data
3929 (add-text-properties (point-min) (point-max) '(rmail-fontified t))
3930 (font-lock-fontify-region (point-min) (point-max))
3931 (and (not modified) (buffer-modified-p) (set-buffer-modified-p nil)))))))
3932 \f
3933 ;;; Speedbar support for RMAIL files.
3934 (eval-when-compile (require 'speedbar))
3935
3936 (defvar rmail-speedbar-match-folder-regexp "^[A-Z0-9]+\\(\\.[A-Z0-9]+\\)?$"
3937 "*This regex is used to match folder names to be displayed in speedbar.
3938 Enabling this will permit speedbar to display your folders for easy
3939 browsing, and moving of messages.")
3940
3941 (defvar rmail-speedbar-last-user nil
3942 "The last user to be displayed in the speedbar.")
3943
3944 (defvar rmail-speedbar-key-map nil
3945 "Keymap used when in rmail display mode.")
3946
3947 (defun rmail-install-speedbar-variables ()
3948 "Install those variables used by speedbar to enhance rmail."
3949 (if rmail-speedbar-key-map
3950 nil
3951 (setq rmail-speedbar-key-map (speedbar-make-specialized-keymap))
3952
3953 (define-key rmail-speedbar-key-map "e" 'speedbar-edit-line)
3954 (define-key rmail-speedbar-key-map "r" 'speedbar-edit-line)
3955 (define-key rmail-speedbar-key-map "\C-m" 'speedbar-edit-line)
3956 (define-key rmail-speedbar-key-map "M"
3957 'rmail-speedbar-move-message-to-folder-on-line)))
3958
3959 (defvar rmail-speedbar-menu-items
3960 '(["Read Folder" speedbar-edit-line t]
3961 ["Move message to folder" rmail-speedbar-move-message-to-folder-on-line
3962 (save-excursion (beginning-of-line)
3963 (looking-at "<M> "))])
3964 "Additional menu-items to add to speedbar frame.")
3965
3966 ;; Make sure our special speedbar major mode is loaded
3967 (if (featurep 'speedbar)
3968 (rmail-install-speedbar-variables)
3969 (add-hook 'speedbar-load-hook 'rmail-install-speedbar-variables))
3970
3971 (defun rmail-speedbar-buttons (buffer)
3972 "Create buttons for BUFFER containing rmail messages.
3973 Click on the address under Reply to: to reply to this person.
3974 Under Folders: Click a name to read it, or on the <M> to move the
3975 current message into that RMAIL folder."
3976 (let ((from nil))
3977 (save-excursion
3978 (set-buffer buffer)
3979 (goto-char (point-min))
3980 (if (not (re-search-forward "^Reply-To: " nil t))
3981 (if (not (re-search-forward "^From:? " nil t))
3982 (setq from t)))
3983 (if from
3984 nil
3985 (setq from (buffer-substring (point) (save-excursion
3986 (end-of-line)
3987 (point))))))
3988 (goto-char (point-min))
3989 (if (and (looking-at "Reply to:")
3990 (equal from rmail-speedbar-last-user))
3991 nil
3992 (setq rmail-speedbar-last-user from)
3993 (erase-buffer)
3994 (insert "Reply To:\n")
3995 (if (stringp from)
3996 (speedbar-insert-button from 'speedbar-directory-face 'highlight
3997 'rmail-speedbar-button 'rmail-reply))
3998 (insert "Folders:\n")
3999 (let* ((case-fold-search nil)
4000 (df (directory-files (save-excursion (set-buffer buffer)
4001 default-directory)
4002 nil rmail-speedbar-match-folder-regexp)))
4003 (while df
4004 (speedbar-insert-button "<M>" 'speedbar-button-face 'highlight
4005 'rmail-speedbar-move-message (car df))
4006 (speedbar-insert-button (car df) 'speedbar-file-face 'highlight
4007 'rmail-speedbar-find-file nil t)
4008 (setq df (cdr df)))))))
4009
4010 (defun rmail-speedbar-button (text token indent)
4011 "Execute an rmail command specified by TEXT.
4012 The command used is TOKEN. INDENT is not used."
4013 (speedbar-with-attached-buffer
4014 (funcall token t)))
4015
4016 (defun rmail-speedbar-find-file (text token indent)
4017 "Load in the rmail file TEXT.
4018 TOKEN and INDENT are not used."
4019 (speedbar-with-attached-buffer
4020 (message "Loading in RMAIL file %s..." text)
4021 (find-file text)))
4022
4023 (defun rmail-speedbar-move-message-to-folder-on-line ()
4024 "If the current line is a folder, move current message to it."
4025 (interactive)
4026 (save-excursion
4027 (beginning-of-line)
4028 (if (re-search-forward "<M> " (save-excursion (end-of-line) (point)) t)
4029 (progn
4030 (forward-char -2)
4031 (speedbar-do-function-pointer)))))
4032
4033 (defun rmail-speedbar-move-message (text token indent)
4034 "From button TEXT, copy current message to the rmail file specified by TOKEN.
4035 TEXT and INDENT are not used."
4036 (speedbar-with-attached-buffer
4037 (message "Moving message to %s" token)
4038 (rmail-output-to-rmail-file token)))
4039
4040 ; Functions for setting, getting and encoding the POP password.
4041 ; The password is encoded to prevent it from being easily accessible
4042 ; to "prying eyes." Obviously, this encoding isn't "real security,"
4043 ; nor is it meant to be.
4044
4045 ;;;###autoload
4046 (defun rmail-set-remote-password (password)
4047 "Set PASSWORD to be used for retrieving mail from a POP or IMAP server."
4048 (interactive "sPassword: ")
4049 (if password
4050 (setq rmail-encoded-remote-password
4051 (rmail-encode-string password (emacs-pid)))
4052 (setq rmail-remote-password nil)
4053 (setq rmail-encoded-remote-password nil)))
4054
4055 (defun rmail-get-remote-password (imap)
4056 "Get the password for retrieving mail from a POP or IMAP server. If none
4057 has been set, then prompt the user for one."
4058 (when (not rmail-encoded-remote-password)
4059 (if (not rmail-remote-password)
4060 (setq rmail-remote-password
4061 (read-passwd (if imap
4062 "IMAP password: "
4063 "POP password: "))))
4064 (rmail-set-remote-password rmail-remote-password)
4065 (setq rmail-remote-password nil))
4066 (rmail-encode-string rmail-encoded-remote-password (emacs-pid)))
4067
4068 (defun rmail-have-password ()
4069 (or rmail-remote-password rmail-encoded-remote-password))
4070
4071 (defun rmail-encode-string (string mask)
4072 "Encode STRING with integer MASK, by taking the exclusive OR of the
4073 lowest byte in the mask with the first character of string, the
4074 second-lowest-byte with the second character of the string, etc.,
4075 restarting at the lowest byte of the mask whenever it runs out.
4076 Returns the encoded string. Calling the function again with an
4077 encoded string (and the same mask) will decode the string."
4078 (setq mask (abs mask)) ; doesn't work if negative
4079 (let* ((string-vector (string-to-vector string)) (i 0)
4080 (len (length string-vector)) (curmask mask) charmask)
4081 (while (< i len)
4082 (if (= curmask 0)
4083 (setq curmask mask))
4084 (setq charmask (% curmask 256))
4085 (setq curmask (lsh curmask -8))
4086 (aset string-vector i (logxor charmask (aref string-vector i)))
4087 (setq i (1+ i)))
4088 (concat string-vector)))
4089
4090 ;;;; Desktop support
4091
4092 (defun rmail-restore-desktop-buffer (desktop-buffer-file-name
4093 desktop-buffer-name
4094 desktop-buffer-misc)
4095 "Restore an rmail buffer specified in a desktop file."
4096 (condition-case error
4097 (progn
4098 (rmail-input desktop-buffer-file-name)
4099 (if (eq major-mode 'rmail-mode)
4100 (current-buffer)
4101 rmail-buffer))
4102 (file-locked
4103 (kill-buffer (current-buffer))
4104 nil)))
4105
4106 (add-to-list 'desktop-buffer-mode-handlers
4107 '(rmail-mode . rmail-restore-desktop-buffer))
4108
4109 (provide 'rmail)
4110
4111 ;;; arch-tag: cff0a950-57fe-4f73-a86e-91ff75afd06c
4112 ;;; rmail.el ends here