* doc/lispref/customize.texi (Custom Themes): Clarify .el preference.
[emacs.git] / lisp / mail / undigest.el
blob8d46be8a808316df31bb23f47b791117a807a437
1 ;;; undigest.el --- digest-cracking support for the RMAIL mail reader -*- lexical-binding:t -*-
3 ;; Copyright (C) 1985-1986, 1994, 1996, 2001-2018 Free Software
4 ;; Foundation, Inc.
6 ;; Maintainer: emacs-devel@gnu.org
7 ;; Keywords: mail
9 ;; This file is part of GNU Emacs.
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 3 of the License, or
14 ;; (at your option) any later version.
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.
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>.
24 ;;; Commentary:
26 ;; See Internet RFC 934 and RFC 1153.
27 ;; Also limited support for MIME digest encapsulation.
29 ;;; Code:
31 (eval-when-compile (require 'cl-lib))
32 (require 'rmail)
34 (defcustom rmail-forward-separator-regex
35 "^----.*\\([Ff]orwarded\\|[Oo]riginal\\).*[Mm]essage"
36 "Regexp to match the string that introduces forwarded messages.
37 This is not a header, but a string contained in the body of the message.
38 You may need to customize it for local needs."
39 :type 'regexp
40 :group 'rmail-headers)
43 (defconst rmail-digest-methods
44 '(rmail-digest-parse-mime
45 rmail-digest-parse-rfc1153strict
46 rmail-digest-parse-rfc1153sloppy
47 rmail-digest-parse-rfc934)
48 "List of digest parsing functions, first tried first.
50 These functions operate on the current narrowing, and take no argument.
51 A function returns nil if it cannot parse the digest. If it can, it
52 returns a list of cons pairs containing the start and end positions of
53 each undigestified message as markers.")
55 (defun rmail-digest-parse-mime ()
56 (goto-char (point-min))
57 (when (let ((head-end (progn (search-forward "\n\n" nil t) (point))))
58 (goto-char (point-min))
59 (and head-end ; FIXME always true
60 (re-search-forward
61 (concat
62 "^Content-type: multipart/digest;"
63 "\\s-* boundary=\"?\\([^\";\n]+\\)[\";\n]")
64 head-end t)
65 (search-forward (match-string 1) nil t)))
66 ;; Ok, prolog separator found
67 (let ((start (make-marker))
68 (end (make-marker))
69 (separator (concat "\n--" (match-string 0) "\n\n"))
70 result)
71 (while (search-forward separator nil t)
72 (move-marker start (match-beginning 0))
73 (move-marker end (match-end 0))
74 (cl-pushnew (cons (copy-marker start) (copy-marker end t))
75 result :test #'equal))
76 ;; Return the list of marker pairs
77 (nreverse result))))
79 (defun rmail-digest-parse-rfc1153strict ()
80 "Parse following strictly the method defined in RFC 1153.
81 See rmail-digest-methods."
82 (rmail-digest-rfc1153
83 "^-\\{70\\}\n\n"
84 "^\n-\\{30\\}\n\n"
85 "^\n-\\{30\\}\n\nEnd of .* Digest.*\n\\*\\{15,\\}\n+\\'"))
87 (defun rmail-digest-parse-rfc1153sloppy ()
88 "Parse using the method defined in RFC 1153, allowing for some sloppiness.
89 See rmail-digest-methods."
90 (rmail-digest-rfc1153
91 "^-\\{55,\\}\n\n"
92 "^\n-\\{27,\\}\n\n"
93 ;; GNU Mailman knowingly (see comment at line 353 of ToDigest.py in
94 ;; Mailman source) produces non-conformant rfc 1153 digests, in that
95 ;; the trailer contains a "digest footer" like this:
96 ;; _______________________________________________
97 ;; <one or more lines of list blurb>
99 ;; End of Foo Digest...
100 ;; **************************************
101 "^\nEnd of"))
103 (defun rmail-digest-rfc1153 (prolog-sep message-sep trailer-sep)
104 (goto-char (point-min))
105 (when (re-search-forward prolog-sep nil t)
106 ;; Ok, prolog separator found
107 (let ((start (make-marker))
108 (end (make-marker))
109 separator result)
110 (move-marker start (match-beginning 0))
111 (move-marker end (match-end 0))
112 (setq result (list (cons (copy-marker start) (copy-marker end t))))
113 (when (re-search-forward message-sep nil t)
114 ;; Ok, at least one message separator found
115 (setq separator (match-string 0))
116 (when (re-search-forward trailer-sep nil t)
117 ;; Wonderful, we found a trailer, too. Now, go on splitting
118 ;; the digest into separate rmail messages
119 (goto-char (cdar result))
120 (while (search-forward separator nil t)
121 (move-marker start (match-beginning 0))
122 (move-marker end (match-end 0))
123 (cl-pushnew (cons (copy-marker start) (copy-marker end t))
124 result :test #'equal))
125 ;; Undo masking of separators inside digestified messages
126 (goto-char (point-min))
127 (while (search-forward
128 (replace-regexp-in-string "\n-" "\n " separator) nil t)
129 (replace-match separator))
130 ;; Return the list of marker pairs
131 (nreverse result))))))
133 (defun rmail-digest-parse-rfc934 ()
134 (goto-char (point-min))
135 (when (re-search-forward "^\n?-[^ ].*\n\n?" nil t)
136 ;; Message separator found
137 (let ((start (make-marker))
138 (end (make-marker))
139 (separator (match-string 0))
140 result)
141 (goto-char (point-min))
142 (while (search-forward separator nil t)
143 (move-marker start (match-beginning 0))
144 (move-marker end (match-end 0))
145 (cl-pushnew (cons (copy-marker start) (copy-marker end t))
146 result :test #'equal))
147 ;; Undo masking of separators inside digestified messages
148 (goto-char (point-min))
149 (while (search-forward "\n- -" nil t)
150 (replace-match "\n-"))
151 ;; Return the list of marker pairs
152 (nreverse result))))
154 (declare-function rmail-update-summary "rmailsum" (&rest ignore))
156 ;;;###autoload
157 (defun undigestify-rmail-message ()
158 "Break up a digest message into its constituent messages.
159 Leaves original message, deleted, before the undigestified messages."
160 (interactive)
161 (set-buffer rmail-buffer)
162 (let ((buff (current-buffer))
163 (current rmail-current-message)
164 (msgbeg (rmail-msgbeg rmail-current-message))
165 (msgend (rmail-msgend rmail-current-message)))
166 (if (rmail-buffers-swapped-p) (set-buffer rmail-view-buffer))
167 (widen)
168 (let ((error t)
169 (buffer-read-only nil))
170 (goto-char msgend)
171 (let ((msg-copy (buffer-substring-no-properties msgbeg msgend)))
172 (narrow-to-region (point) (point))
173 (insert "\n" msg-copy))
174 (goto-char (point-min))
175 (unwind-protect
176 (progn
177 (let ((fill-prefix "")
178 (case-fold-search t)
179 digest-name fun-list sep-list start end)
180 (setq digest-name (mail-strip-quoted-names
181 (save-restriction
182 (search-forward "\n\n" nil 'move)
183 (narrow-to-region (point-min) (point))
184 (or (mail-fetch-field "Reply-To")
185 (mail-fetch-field "To")
186 (mail-fetch-field "Apparently-To")
187 (mail-fetch-field "From")))))
188 (unless digest-name
189 (error "Message is not a digest--bad header"))
190 (setq fun-list rmail-digest-methods)
191 (while (and fun-list
192 (null (setq sep-list (funcall (car fun-list)))))
193 (setq fun-list (cdr fun-list)))
194 (unless sep-list
195 (error "Message is not a digest--no messages found"))
196 ;; Split the digest into separate rmail messages.
197 (while sep-list
198 (setq start (caar sep-list)
199 end (cdar sep-list))
200 (delete-region start end)
201 (goto-char start)
202 (search-forward "\n\n" (caar (cdr sep-list)) 'move)
203 (save-restriction
204 (narrow-to-region end (point))
205 (goto-char (point-min))
206 (insert "\nFrom rmail@localhost " (current-time-string) "\n")
207 (save-excursion
208 (forward-line -1)
209 (rmail-add-mbox-headers))
210 (unless (mail-fetch-field "To")
211 (insert "To: " digest-name "\n")))
212 (set-marker start nil)
213 (set-marker end nil)
214 (setq sep-list (cdr sep-list))))
215 (setq error nil)
216 (message "Message successfully undigestified")
217 (set-buffer buff)
218 (rmail-swap-buffers-maybe)
219 (goto-char (point-max))
220 ;; FIXME use rmail-count-new-messages.
221 (rmail-set-message-counters)
222 (set-buffer-modified-p t)
223 (rmail-show-message current)
224 (rmail-delete-forward)
225 (if (rmail-summary-exists)
226 (rmail-select-summary (rmail-update-summary))))
227 (when error
228 (delete-region (point-min) (point-max))
229 (set-buffer buff)
230 (rmail-show-message current))))))
232 ;;;###autoload
233 (defun unforward-rmail-message ()
234 "Extract a forwarded message from the containing message.
235 This puts the forwarded message into a separate rmail message following
236 the containing message. This command is only useful when messages are
237 forwarded with `rmail-enable-mime-composing' set to nil."
238 (interactive)
239 (set-buffer rmail-buffer)
240 (let ((buff (current-buffer))
241 (current rmail-current-message)
242 (beg (rmail-msgbeg rmail-current-message))
243 (msgend (rmail-msgend rmail-current-message))
244 (error t))
245 (unwind-protect
246 (progn
247 (if (rmail-buffers-swapped-p) (set-buffer rmail-view-buffer))
248 (widen)
249 (goto-char beg)
250 (search-forward "\n\n" msgend)
251 (narrow-to-region beg (point))
252 (let ((old-fwd-from (mail-fetch-field "Forwarded-From" nil nil t))
253 (old-fwd-date (mail-fetch-field "Forwarded-Date" nil nil t))
254 (fwd-from (mail-fetch-field "From"))
255 (fwd-date (mail-fetch-field "Date"))
256 (buffer-read-only nil)
257 prefix forward-msg end)
258 (widen)
259 (narrow-to-region beg msgend)
260 (cond ((re-search-forward rmail-forward-separator-regex nil t)
261 (forward-line 1)
262 (skip-chars-forward "\n")
263 (setq beg (point))
264 (setq end (if (re-search-forward "^----.*[^- \t\n]" nil t)
265 (match-beginning 0) (point-max)))
266 (setq forward-msg
267 (replace-regexp-in-string
268 "^- -" "-" (buffer-substring beg end))))
269 ((and (re-search-forward "^\\(> ?\\)[a-zA-Z-]+: .*\n" nil t)
270 (setq beg (match-beginning 0))
271 (setq prefix (match-string-no-properties 1))
272 (goto-char beg)
273 (looking-at (concat "\\(" prefix ".+\n\\)*"
274 prefix "Date: ."))
275 (looking-at (concat "\\(" prefix ".+\n\\)*"
276 prefix "From: .+\n"
277 "\\(" prefix ".+\n\\)*"
278 "\\(> ?\\)?\n" prefix)))
279 (re-search-forward "^[^>\n]" nil 'move)
280 (backward-char)
281 (skip-chars-backward " \t\n")
282 (forward-line 1)
283 (setq end (point))
284 (setq forward-msg
285 (replace-regexp-in-string
286 (if (string= prefix ">") "^>" "> ?")
287 "" (buffer-substring beg end))))
289 (error "No forwarded message found")))
290 (widen)
291 (goto-char msgend)
292 ;; Insert a fake From line.
293 ;; FIXME we could construct one using the From and Date headers
294 ;; of the forwarded message - is it worth it?
295 (insert "\n\nFrom rmail@localhost " (current-time-string) "\n")
296 (setq beg (point)) ; start of header
297 (while old-fwd-from
298 (insert "Forwarded-From: " (car old-fwd-from) "\n")
299 (insert "Forwarded-Date: " (car old-fwd-date) "\n")
300 (setq old-fwd-from (cdr old-fwd-from))
301 (setq old-fwd-date (cdr old-fwd-date)))
302 (insert "Forwarded-From: " fwd-from "\n")
303 (insert "Forwarded-Date: " fwd-date "\n")
304 (insert forward-msg "\n")
305 (goto-char beg)
306 (re-search-forward "\n$" nil 'move) ; end of header
307 (narrow-to-region beg (point))
308 (goto-char (point-min))
309 (while (not (eobp))
310 (unless (looking-at "^[a-zA-Z-]+: ")
311 (insert "\t"))
312 (forward-line))
313 (widen)
314 (goto-char beg)
315 (forward-line -1)
316 (rmail-add-mbox-headers)) ; marks as unseen
317 (setq error nil)
318 (set-buffer buff)
319 (rmail-swap-buffers-maybe)
320 (goto-char (point-max))
321 ;; FIXME use rmail-count-new-messages.
322 (rmail-set-message-counters)
323 (set-buffer-modified-p t)
324 (rmail-show-message current)
325 (if (rmail-summary-exists)
326 (rmail-select-summary (rmail-update-summary))))
327 (when error
328 (set-buffer buff)
329 (rmail-show-message current)))))
331 (provide 'undigest)
333 ;; Local Variables:
334 ;; generated-autoload-file: "rmail-loaddefs.el"
335 ;; End:
337 ;;; undigest.el ends here