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
6 ;; Maintainer: emacs-devel@gnu.org
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/>.
26 ;; See Internet RFC 934 and RFC 1153.
27 ;; Also limited support for MIME digest encapsulation.
31 (eval-when-compile (require 'cl-lib
))
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."
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
62 "^Content-type: multipart/digest;"
63 "\\s-* boundary=\"?\\([^\";\n]+\\)[\";\n]")
65 (search-forward (match-string 1) nil t
)))
66 ;; Ok, prolog separator found
67 (let ((start (make-marker))
69 (separator (concat "\n--" (match-string 0) "\n\n"))
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
79 (defun rmail-digest-parse-rfc1153strict ()
80 "Parse following strictly the method defined in RFC 1153.
81 See rmail-digest-methods."
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."
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 ;; **************************************
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))
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))
139 (separator (match-string 0))
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
154 (declare-function rmail-update-summary
"rmailsum" (&rest ignore
))
157 (defun undigestify-rmail-message ()
158 "Break up a digest message into its constituent messages.
159 Leaves original message, deleted, before the undigestified messages."
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
))
169 (buffer-read-only nil
))
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))
177 (let ((fill-prefix "")
179 digest-name fun-list sep-list start end
)
180 (setq digest-name
(mail-strip-quoted-names
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")))))
189 (error "Message is not a digest--bad header"))
190 (setq fun-list rmail-digest-methods
)
192 (null (setq sep-list
(funcall (car fun-list
)))))
193 (setq fun-list
(cdr fun-list
)))
195 (error "Message is not a digest--no messages found"))
196 ;; Split the digest into separate rmail messages.
198 (setq start
(caar sep-list
)
200 (delete-region start end
)
202 (search-forward "\n\n" (caar (cdr sep-list
)) 'move
)
204 (narrow-to-region end
(point))
205 (goto-char (point-min))
206 (insert "\nFrom rmail@localhost " (current-time-string) "\n")
209 (rmail-add-mbox-headers))
210 (unless (mail-fetch-field "To")
211 (insert "To: " digest-name
"\n")))
212 (set-marker start nil
)
214 (setq sep-list
(cdr sep-list
))))
216 (message "Message successfully undigestified")
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))))
228 (delete-region (point-min) (point-max))
230 (rmail-show-message current
))))))
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."
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
))
247 (if (rmail-buffers-swapped-p) (set-buffer rmail-view-buffer
))
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
)
259 (narrow-to-region beg msgend
)
260 (cond ((re-search-forward rmail-forward-separator-regex nil t
)
262 (skip-chars-forward "\n")
264 (setq end
(if (re-search-forward "^----.*[^- \t\n]" nil t
)
265 (match-beginning 0) (point-max)))
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))
273 (looking-at (concat "\\(" prefix
".+\n\\)*"
275 (looking-at (concat "\\(" prefix
".+\n\\)*"
277 "\\(" prefix
".+\n\\)*"
278 "\\(> ?\\)?\n" prefix
)))
279 (re-search-forward "^[^>\n]" nil
'move
)
281 (skip-chars-backward " \t\n")
285 (replace-regexp-in-string
286 (if (string= prefix
">") "^>" "> ?")
287 "" (buffer-substring beg end
))))
289 (error "No forwarded message found")))
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
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")
306 (re-search-forward "\n$" nil
'move
) ; end of header
307 (narrow-to-region beg
(point))
308 (goto-char (point-min))
310 (unless (looking-at "^[a-zA-Z-]+: ")
316 (rmail-add-mbox-headers)) ; marks as unseen
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))))
329 (rmail-show-message current
)))))
334 ;; generated-autoload-file: "rmail-loaddefs.el"
337 ;;; undigest.el ends here