* help-fns.el (describe-variable): Fix 2010-12-17 change (Bug#7511).
[bpt/emacs.git] / lisp / mail / rmailmm.el
CommitLineData
537ab246
BG
1;;; rmailmm.el --- MIME decoding and display stuff for RMAIL
2
114f9c96 3;; Copyright (C) 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
537ab246 4
6ea97e7e
GM
5;; Author: Alexander Pohoyda
6;; Alex Schroeder
537ab246
BG
7;; Maintainer: FSF
8;; Keywords: mail
9
10;; This file is part of GNU Emacs.
11
12;; GNU Emacs is free software: you can redistribute it and/or modify
13;; it under the terms of the GNU General Public License as published by
14;; the Free Software Foundation, either version 3 of the License, or
15;; (at your option) any later version.
16
17;; GNU Emacs is distributed in the hope that it will be useful,
18;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20;; GNU General Public License for more details.
21
22;; You should have received a copy of the GNU General Public License
23;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
24
25;;; Commentary:
26
27;; Essentially based on the design of Alexander Pohoyda's MIME
73422054 28;; extensions (mime-display.el and mime.el).
d1be4ec2
KH
29
30;; This file provides two operation modes for viewing a MIME message.
31
32;; (1) When rmail-enable-mime is non-nil (now it is the default), the
33;; function `rmail-show-mime' is automatically called. That function
34;; shows a MIME message directly in RMAIL's view buffer.
35
36;; (2) When rmail-enable-mime is nil, the command 'v' (or M-x
37;; rmail-mime) shows a MIME message in a new buffer "*RMAIL*".
38
39;; Both operations share the intermediate functions rmail-mime-process
40;; and rmail-mime-process-multipart as below.
41
42;; rmail-show-mime
43;; +- rmail-mime-parse
44;; | +- rmail-mime-process <--+------------+
45;; | | +---------+ |
46;; | + rmail-mime-process-multipart --+
47;; |
48;; + rmail-mime-insert <----------------+
49;; +- rmail-mime-insert-text |
50;; +- rmail-mime-insert-bulk |
51;; +- rmail-mime-insert-multipart --+
52;;
53;; rmail-mime
54;; +- rmail-mime-show <----------------------------------+
55;; +- rmail-mime-process |
56;; +- rmail-mime-handle |
57;; +- rmail-mime-text-handler |
58;; +- rmail-mime-bulk-handler |
59;; | + rmail-mime-insert-bulk
60;; +- rmail-mime-multipart-handler |
61;; +- rmail-mime-process-multipart --+
62
63;; In addition, for the case of rmail-enable-mime being non-nil, this
64;; file provides two functions rmail-insert-mime-forwarded-message and
65;; rmail-insert-mime-resent-message for composing forwarded and resent
66;; messages respectively.
537ab246 67
2e9075d3
GM
68;; Todo:
69
d1be4ec2
KH
70;; Make rmail-mime-media-type-handlers-alist usable in the first
71;; operation mode.
72;; Handle multipart/alternative in the second operation mode.
a92cdd49 73;; Offer the option to call external/internal viewers (doc-view, xpdf, etc).
2e9075d3 74
537ab246
BG
75;;; Code:
76
77(require 'rmail)
78(require 'mail-parse)
d1be4ec2 79(require 'message)
537ab246 80
f4ce6150 81;;; User options.
537ab246 82
e8652dd9
GM
83(defgroup rmail-mime nil
84 "Rmail MIME handling options."
85 :prefix "rmail-mime-"
86 :group 'rmail)
87
537ab246
BG
88(defcustom rmail-mime-media-type-handlers-alist
89 '(("multipart/.*" rmail-mime-multipart-handler)
90 ("text/.*" rmail-mime-text-handler)
91 ("text/\\(x-\\)?patch" rmail-mime-bulk-handler)
e8652dd9 92 ("\\(image\\|audio\\|video\\|application\\)/.*" rmail-mime-bulk-handler))
f4ce6150 93 "Functions to handle various content types.
c8644de0
GM
94This is an alist with elements of the form (REGEXP FUNCTION ...).
95The first item is a regular expression matching a content-type.
96The remaining elements are handler functions to run, in order of
2e9075d3
GM
97decreasing preference. These are called until one returns non-nil.
98Note that this only applies to items with an inline Content-Disposition,
99all others are handled by `rmail-mime-bulk-handler'."
c8644de0 100 :type '(alist :key-type regexp :value-type (repeat function))
e8652dd9
GM
101 :version "23.1"
102 :group 'rmail-mime)
537ab246
BG
103
104(defcustom rmail-mime-attachment-dirs-alist
f265b4de 105 `(("text/.*" "~/Documents")
537ab246 106 ("image/.*" "~/Pictures")
f265b4de 107 (".*" "~/Desktop" "~" ,temporary-file-directory))
f4ce6150
GM
108 "Default directories to save attachments of various types into.
109This is an alist with elements of the form (REGEXP DIR ...).
110The first item is a regular expression matching a content-type.
111The remaining elements are directories, in order of decreasing preference.
112The first directory that exists is used."
113 :type '(alist :key-type regexp :value-type (repeat directory))
114 :version "23.1"
e8652dd9
GM
115 :group 'rmail-mime)
116
117(defcustom rmail-mime-show-images 'button
118 "What to do with image attachments that Emacs is capable of displaying.
119If nil, do nothing special. If `button', add an extra button
a92cdd49
GM
120that when pushed displays the image in the buffer. If a number,
121automatically show images if they are smaller than that size (in
122bytes), otherwise add a display button. Anything else means to
123automatically display the image in the buffer."
e8652dd9
GM
124 :type '(choice (const :tag "Add button to view image" button)
125 (const :tag "No special treatment" nil)
a92cdd49 126 (number :tag "Show if smaller than certain size")
e8652dd9
GM
127 (other :tag "Always show" show))
128 :version "23.2"
129 :group 'rmail-mime)
537ab246 130
f4ce6150
GM
131;;; End of user options.
132
d1be4ec2
KH
133;;; MIME-entity object
134
135(defun rmail-mime-entity (type disposition transfer-encoding
136 header body children)
137 "Retrun a newly created MIME-entity object.
138
139A MIME-entity is a vector of 6 elements:
140
141 [ TYPE DISPOSITION TRANSFER-ENCODING HEADER BODY CHILDREN ]
142
143TYPE and DISPOSITION correspond to MIME headers Content-Type: and
144Cotent-Disposition: respectively, and has this format:
145
146 \(VALUE (ATTRIBUTE . VALUE) (ATTRIBUTE . VALUE) ...)
147
148VALUE is a string and ATTRIBUTE is a symbol.
149
150Consider the following header, for example:
151
152Content-Type: multipart/mixed;
153 boundary=\"----=_NextPart_000_0104_01C617E4.BDEC4C40\"
154
155The corresponding TYPE argument must be:
156
157\(\"multipart/mixed\"
158 \(\"boundary\" . \"----=_NextPart_000_0104_01C617E4.BDEC4C40\"))
159
160TRANSFER-ENCODING corresponds to MIME header
161Content-Transfer-Encoding, and is a lowercased string.
162
163HEADER and BODY are a cons (BEG . END), where BEG and END specify
164the region of the corresponding part in RMAIL's data (mbox)
165buffer. BODY may be nil. In that case, the current buffer is
166narrowed to the body part.
167
168CHILDREN is a list of MIME-entities for a \"multipart\" entity, and
169nil for the other types."
170 (vector type disposition transfer-encoding header body children))
171
172;; Accessors for a MIME-entity object.
173(defsubst rmail-mime-entity-type (entity) (aref entity 0))
174(defsubst rmail-mime-entity-disposition (entity) (aref entity 1))
175(defsubst rmail-mime-entity-transfer-encoding (entity) (aref entity 2))
176(defsubst rmail-mime-entity-header (entity) (aref entity 3))
177(defsubst rmail-mime-entity-body (entity) (aref entity 4))
178(defsubst rmail-mime-entity-children (entity) (aref entity 5))
f4ce6150 179
537ab246
BG
180;;; Buttons
181
182(defun rmail-mime-save (button)
183 "Save the attachment using info in the BUTTON."
184 (let* ((filename (button-get button 'filename))
185 (directory (button-get button 'directory))
fe6793d4 186 (data (button-get button 'data))
d1be4ec2 187 (mbox-buf rmail-view-buffer)
fe6793d4 188 (ofilename filename))
537ab246
BG
189 (setq filename (expand-file-name
190 (read-file-name (format "Save as (default: %s): " filename)
191 directory
192 (expand-file-name filename directory))
193 directory))
fe6793d4
GM
194 ;; If arg is just a directory, use the default file name, but in
195 ;; that directory (copied from write-file).
196 (if (file-directory-p filename)
197 (setq filename (expand-file-name
198 (file-name-nondirectory ofilename)
199 (file-name-as-directory filename))))
200 (with-temp-buffer
537ab246 201 (set-buffer-file-coding-system 'no-conversion)
134a027f
EZ
202 ;; Needed e.g. by jka-compr, so if the attachment is a compressed
203 ;; file, the magic signature compares equal with the unibyte
204 ;; signature string recorded in jka-compr-compression-info-list.
205 (set-buffer-multibyte nil)
d1be4ec2
KH
206 (setq buffer-undo-list t)
207 (if (stringp data)
208 (insert data)
209 ;; DATA is a MIME-entity object.
210 (let ((transfer-encoding (rmail-mime-entity-transfer-encoding data))
211 (body (rmail-mime-entity-body data)))
212 (insert-buffer-substring mbox-buf (car body) (cdr body))
213 (cond ((string= transfer-encoding "base64")
214 (ignore-errors (base64-decode-region (point-min) (point-max))))
215 ((string= transfer-encoding "quoted-printable")
216 (quoted-printable-decode-region (point-min) (point-max))))))
fe6793d4 217 (write-region nil nil filename nil nil nil t))))
537ab246 218
fe6793d4 219(define-button-type 'rmail-mime-save 'action 'rmail-mime-save)
537ab246
BG
220
221;;; Handlers
222
223(defun rmail-mime-text-handler (content-type
224 content-disposition
225 content-transfer-encoding)
226 "Handle the current buffer as a plain text MIME part."
227 (let* ((charset (cdr (assq 'charset (cdr content-type))))
228 (coding-system (when charset
229 (intern (downcase charset)))))
230 (when (coding-system-p coding-system)
231 (decode-coding-region (point-min) (point-max) coding-system))))
232
d1be4ec2
KH
233(defun rmail-mime-insert-text (entity)
234 "Insert MIME-entity ENTITY as a plain text MIME part in the current buffer."
235 (let* ((content-type (rmail-mime-entity-type entity))
236 (charset (cdr (assq 'charset (cdr content-type))))
237 (coding-system (if charset (intern (downcase charset))))
238 (transfer-encoding (rmail-mime-entity-transfer-encoding entity))
239 (body (rmail-mime-entity-body entity)))
240 (save-restriction
241 (narrow-to-region (point) (point))
242 (insert-buffer-substring rmail-buffer (car body) (cdr body))
243 (cond ((string= transfer-encoding "base64")
244 (ignore-errors (base64-decode-region (point-min) (point-max))))
245 ((string= transfer-encoding "quoted-printable")
246 (quoted-printable-decode-region (point-min) (point-max))))
247 (if (coding-system-p coding-system)
248 (decode-coding-region (point-min) (point-max) coding-system)))))
249
f4ce6150 250;; FIXME move to the test/ directory?
537ab246
BG
251(defun test-rmail-mime-handler ()
252 "Test of a mail using no MIME parts at all."
253 (let ((mail "To: alex@gnu.org
254Content-Type: text/plain; charset=koi8-r
255Content-Transfer-Encoding: 8bit
256MIME-Version: 1.0
257
258\372\304\322\301\327\323\324\327\325\312\324\305\41"))
259 (switch-to-buffer (get-buffer-create "*test*"))
260 (erase-buffer)
261 (set-buffer-multibyte nil)
262 (insert mail)
263 (rmail-mime-show t)
264 (set-buffer-multibyte t)))
265
e8652dd9
GM
266
267(defun rmail-mime-insert-image (type data)
d1be4ec2
KH
268 "Insert an image of type TYPE, where DATA is the image data.
269If DATA is not a string, it is a MIME-entity object."
e8652dd9 270 (end-of-line)
d1be4ec2
KH
271 (let ((modified (buffer-modified-p)))
272 (insert ?\n)
273 (unless (stringp data)
274 ;; DATA is a MIME-entity.
275 (let ((transfer-encoding (rmail-mime-entity-transfer-encoding data))
276 (body (rmail-mime-entity-body data))
277 (mbox-buffer rmail-view-buffer))
278 (with-temp-buffer
279 (set-buffer-multibyte nil)
280 (setq buffer-undo-list t)
281 (insert-buffer-substring mbox-buffer (car body) (cdr body))
282 (cond ((string= transfer-encoding "base64")
283 (ignore-errors (base64-decode-region (point-min) (point-max))))
284 ((string= transfer-encoding "quoted-printable")
285 (quoted-printable-decode-region (point-min) (point-max))))
286 (setq data
287 (buffer-substring-no-properties (point-min) (point-max))))))
288 (insert-image (create-image data type t))
289 (set-buffer-modified-p modified)))
e8652dd9
GM
290
291(defun rmail-mime-image (button)
292 "Display the image associated with BUTTON."
293 (let ((inhibit-read-only t))
294 (rmail-mime-insert-image (button-get button 'image-type)
295 (button-get button 'image-data))))
296
297(define-button-type 'rmail-mime-image 'action 'rmail-mime-image)
298
299
537ab246
BG
300(defun rmail-mime-bulk-handler (content-type
301 content-disposition
e8652dd9 302 content-transfer-encoding)
2e9075d3 303 "Handle the current buffer as an attachment to download.
e8652dd9
GM
304For images that Emacs is capable of displaying, the behavior
305depends upon the value of `rmail-mime-show-images'."
d1be4ec2
KH
306 (rmail-mime-insert-bulk
307 (rmail-mime-entity content-type content-disposition content-transfer-encoding
308 nil nil nil)))
309
310(defun rmail-mime-insert-bulk (entity)
311 "Inesrt a MIME-entity ENTITY as an attachment.
312The optional second arg DATA, if non-nil, is a string containing
313the attachment data that is already decoded."
e8652dd9 314 ;; Find the default directory for this media type.
d1be4ec2
KH
315 (let* ((content-type (rmail-mime-entity-type entity))
316 (content-disposition (rmail-mime-entity-disposition entity))
317 (body (rmail-mime-entity-body entity))
318 (directory (catch 'directory
537ab246
BG
319 (dolist (entry rmail-mime-attachment-dirs-alist)
320 (when (string-match (car entry) (car content-type))
321 (dolist (dir (cdr entry))
322 (when (file-directory-p dir)
323 (throw 'directory dir)))))))
324 (filename (or (cdr (assq 'name (cdr content-type)))
325 (cdr (assq 'filename (cdr content-disposition)))
326 "noname"))
327 (label (format "\nAttached %s file: " (car content-type)))
69220882 328 (units '(B kB MB GB))
d1be4ec2
KH
329 data udata size osize type)
330 (if body
331 (setq data entity
332 udata entity
333 size (- (cdr body) (car body)))
334 (setq data (buffer-string)
335 udata (string-as-unibyte data)
336 size (length udata))
337 (delete-region (point-min) (point-max)))
338 (setq osize size)
339 (while (and (> size 1024.0) ; cribbed from gnus-agent-expire-done-message
69220882
GM
340 (cdr units))
341 (setq size (/ size 1024.0)
342 units (cdr units)))
537ab246
BG
343 (insert label)
344 (insert-button filename
345 :type 'rmail-mime-save
2e9075d3 346 'help-echo "mouse-2, RET: Save attachment"
537ab246 347 'filename filename
fe6793d4 348 'directory (file-name-as-directory directory)
2e9075d3 349 'data data)
69220882 350 (insert (format " (%.0f%s)" size (car units)))
e8652dd9
GM
351 (when (and rmail-mime-show-images
352 (string-match "image/\\(.*\\)" (setq type (car content-type)))
353 (setq type (concat "." (match-string 1 type))
354 type (image-type-from-file-name type))
355 (memq type image-types)
356 (image-type-available-p type))
2e9075d3 357 (insert " ")
a92cdd49
GM
358 (cond ((or (eq rmail-mime-show-images 'button)
359 (and (numberp rmail-mime-show-images)
360 (>= osize rmail-mime-show-images)))
e8652dd9
GM
361 (insert-button "Display"
362 :type 'rmail-mime-image
363 'help-echo "mouse-2, RET: Show image"
364 'image-type type
69220882 365 'image-data udata))
e8652dd9 366 (t
69220882 367 (rmail-mime-insert-image type udata))))))
537ab246
BG
368
369(defun test-rmail-mime-bulk-handler ()
370 "Test of a mail used as an example in RFC 2183."
371 (let ((mail "Content-Type: image/jpeg
372Content-Disposition: attachment; filename=genome.jpeg;
373 modification-date=\"Wed, 12 Feb 1997 16:29:51 -0500\";
374Content-Description: a complete map of the human genome
375Content-Transfer-Encoding: base64
376
377iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAABGdBTUEAALGPC/xhBQAAAAZQ
378TFRF////AAAAVcLTfgAAAPZJREFUeNq9ldsOwzAIQ+3//+l1WlvA5ZLsoUiTto4TB+ISoAjy
379+ITfRBfcAmgRFFeAm+J6uhdKdFhFWUgDkFsK0oUp/9G2//Kj7Jx+5tSKOdBscgUYiKHRS/me
380WATQdRUvAK0Bnmshmtn79PpaLBbbOZkjKvRnjRZoRswOkG1wFchKew2g9wXVJVZL/m4+B+vv
3819AxQQR2Q33SgAYJzzVACdAWjAfRYzYFO9n6SLnydtQHSMxYDMAKqZ/8FS/lTK+zuq3CtK64L
382UDwbgUEAUmk2Zyg101d6PhCDySgAvTvDgKiuOrc4dLxUb7UMnhGIexyI+d6U+ABuNAP4Simx
383lgAAAABJRU5ErkJggg==
384"))
385 (switch-to-buffer (get-buffer-create "*test*"))
386 (erase-buffer)
387 (insert mail)
388 (rmail-mime-show)))
389
390(defun rmail-mime-multipart-handler (content-type
391 content-disposition
392 content-transfer-encoding)
393 "Handle the current buffer as a multipart MIME body.
394The current buffer should be narrowed to the body. CONTENT-TYPE,
395CONTENT-DISPOSITION, and CONTENT-TRANSFER-ENCODING are the values
396of the respective parsed headers. See `rmail-mime-handle' for their
397format."
d1be4ec2
KH
398 (rmail-mime-process-multipart
399 content-type content-disposition content-transfer-encoding nil))
400
401(defun rmail-mime-process-multipart (content-type
402 content-disposition
403 content-transfer-encoding
404 parse-only)
405 "Process the current buffer as a multipart MIME body.
406
407If PARSE-ONLY is nil, modify the current buffer directly for showing
408the MIME body and return nil.
409
410Otherwise, just parse the current buffer and return a list of
411MIME-entity objects.
412
413The other arguments are the same as `rmail-mime-multipart-handler'."
537ab246
BG
414 ;; Some MUAs start boundaries with "--", while it should start
415 ;; with "CRLF--", as defined by RFC 2046:
416 ;; The boundary delimiter MUST occur at the beginning of a line,
417 ;; i.e., following a CRLF, and the initial CRLF is considered to
418 ;; be attached to the boundary delimiter line rather than part
419 ;; of the preceding part.
420 ;; We currently don't handle that.
421 (let ((boundary (cdr (assq 'boundary content-type)))
d1be4ec2 422 beg end next entities)
537ab246
BG
423 (unless boundary
424 (rmail-mm-get-boundary-error-message
425 "No boundary defined" content-type content-disposition
426 content-transfer-encoding))
427 (setq boundary (concat "\n--" boundary))
428 ;; Hide the body before the first bodypart
429 (goto-char (point-min))
430 (when (and (search-forward boundary nil t)
431 (looking-at "[ \t]*\n"))
d1be4ec2
KH
432 (if parse-only
433 (narrow-to-region (match-end 0) (point-max))
434 (delete-region (point-min) (match-end 0))))
537ab246
BG
435 ;; Loop over all body parts, where beg points at the beginning of
436 ;; the part and end points at the end of the part. next points at
437 ;; the beginning of the next part.
438 (setq beg (point-min))
439 (while (search-forward boundary nil t)
440 (setq end (match-beginning 0))
441 ;; If this is the last boundary according to RFC 2046, hide the
442 ;; epilogue, else hide the boundary only. Use a marker for
443 ;; `next' because `rmail-mime-show' may change the buffer.
ffa1fed6 444 (cond ((looking-at "--[ \t]*$")
537ab246
BG
445 (setq next (point-max-marker)))
446 ((looking-at "[ \t]*\n")
ffa1fed6 447 (setq next (copy-marker (match-end 0) t)))
537ab246
BG
448 (t
449 (rmail-mm-get-boundary-error-message
450 "Malformed boundary" content-type content-disposition
451 content-transfer-encoding)))
537ab246 452 ;; Handle the part.
d1be4ec2
KH
453 (if parse-only
454 (save-restriction
455 (narrow-to-region beg end)
456 (setq entities (cons (rmail-mime-process nil t) entities)))
457 (delete-region end next)
458 (save-restriction
459 (narrow-to-region beg end)
460 (rmail-mime-show)))
461 (goto-char (setq beg next)))
462 (nreverse entities)))
537ab246
BG
463
464(defun test-rmail-mime-multipart-handler ()
465 "Test of a mail used as an example in RFC 2046."
466 (let ((mail "From: Nathaniel Borenstein <nsb@bellcore.com>
467To: Ned Freed <ned@innosoft.com>
468Date: Sun, 21 Mar 1993 23:56:48 -0800 (PST)
469Subject: Sample message
470MIME-Version: 1.0
471Content-type: multipart/mixed; boundary=\"simple boundary\"
472
473This is the preamble. It is to be ignored, though it
474is a handy place for composition agents to include an
475explanatory note to non-MIME conformant readers.
476
477--simple boundary
478
479This is implicitly typed plain US-ASCII text.
480It does NOT end with a linebreak.
481--simple boundary
482Content-type: text/plain; charset=us-ascii
483
484This is explicitly typed plain US-ASCII text.
485It DOES end with a linebreak.
486
487--simple boundary--
488
489This is the epilogue. It is also to be ignored."))
490 (switch-to-buffer (get-buffer-create "*test*"))
491 (erase-buffer)
492 (insert mail)
493 (rmail-mime-show t)))
494
495;;; Main code
496
497(defun rmail-mime-handle (content-type
498 content-disposition
499 content-transfer-encoding)
500 "Handle the current buffer as a MIME part.
501The current buffer should be narrowed to the respective body, and
502point should be at the beginning of the body.
503
504CONTENT-TYPE, CONTENT-DISPOSITION, and CONTENT-TRANSFER-ENCODING
0c9ff2c5
GM
505are the values of the respective parsed headers. The latter should
506be downcased. The parsed headers for CONTENT-TYPE and CONTENT-DISPOSITION
507have the form
537ab246
BG
508
509 \(VALUE . ALIST)
510
511In other words:
512
513 \(VALUE (ATTRIBUTE . VALUE) (ATTRIBUTE . VALUE) ...)
514
515VALUE is a string and ATTRIBUTE is a symbol.
516
517Consider the following header, for example:
518
519Content-Type: multipart/mixed;
520 boundary=\"----=_NextPart_000_0104_01C617E4.BDEC4C40\"
521
522The parsed header value:
523
524\(\"multipart/mixed\"
525 \(\"boundary\" . \"----=_NextPart_000_0104_01C617E4.BDEC4C40\"))"
526 ;; Handle the content transfer encodings we know. Unknown transfer
527 ;; encodings will be passed on to the various handlers.
528 (cond ((string= content-transfer-encoding "base64")
529 (when (ignore-errors
530 (base64-decode-region (point) (point-max)))
531 (setq content-transfer-encoding nil)))
532 ((string= content-transfer-encoding "quoted-printable")
533 (quoted-printable-decode-region (point) (point-max))
534 (setq content-transfer-encoding nil))
535 ((string= content-transfer-encoding "8bit")
536 ;; FIXME: Is this the correct way?
c893016b
SM
537 ;; No, of course not, it just means there's no decoding to do.
538 ;; (set-buffer-multibyte nil)
539 (setq content-transfer-encoding nil)
540 ))
537ab246
BG
541 ;; Inline stuff requires work. Attachments are handled by the bulk
542 ;; handler.
543 (if (string= "inline" (car content-disposition))
544 (let ((stop nil))
545 (dolist (entry rmail-mime-media-type-handlers-alist)
546 (when (and (string-match (car entry) (car content-type)) (not stop))
547 (progn
548 (setq stop (funcall (cadr entry) content-type
549 content-disposition
550 content-transfer-encoding))))))
551 ;; Everything else is an attachment.
552 (rmail-mime-bulk-handler content-type
553 content-disposition
554 content-transfer-encoding)))
555
556(defun rmail-mime-show (&optional show-headers)
557 "Handle the current buffer as a MIME message.
558If SHOW-HEADERS is non-nil, then the headers of the current part
559will shown as usual for a MIME message. The headers are also
560shown for the content type message/rfc822. This function will be
561called recursively if multiple parts are available.
562
563The current buffer must contain a single message. It will be
564modified."
d1be4ec2
KH
565 (rmail-mime-process show-headers nil))
566
567(defun rmail-mime-process (show-headers parse-only)
537ab246
BG
568 (let ((end (point-min))
569 content-type
570 content-transfer-encoding
571 content-disposition)
572 ;; `point-min' returns the beginning and `end' points at the end
573 ;; of the headers.
574 (goto-char (point-min))
575 ;; If we're showing a part without headers, then it will start
576 ;; with a newline.
577 (if (eq (char-after) ?\n)
578 (setq end (1+ (point)))
579 (when (search-forward "\n\n" nil t)
580 (setq end (match-end 0))
581 (save-restriction
582 (narrow-to-region (point-min) end)
583 ;; FIXME: Default disposition of the multipart entities should
584 ;; be inherited.
585 (setq content-type
586 (mail-fetch-field "Content-Type")
587 content-transfer-encoding
588 (mail-fetch-field "Content-Transfer-Encoding")
589 content-disposition
590 (mail-fetch-field "Content-Disposition")))))
0c9ff2c5
GM
591 ;; Per RFC 2045, C-T-E is case insensitive (bug#5070), but the others
592 ;; are not completely so. Hopefully mail-header-parse-* DTRT.
593 (if content-transfer-encoding
594 (setq content-transfer-encoding (downcase content-transfer-encoding)))
595 (setq content-type
596 (if content-type
597 (mail-header-parse-content-type content-type)
598 ;; FIXME: Default "message/rfc822" in a "multipart/digest"
599 ;; according to RFC 2046.
600 '("text/plain")))
537ab246
BG
601 (setq content-disposition
602 (if content-disposition
603 (mail-header-parse-content-disposition content-disposition)
604 ;; If none specified, we are free to choose what we deem
605 ;; suitable according to RFC 2183. We like inline.
606 '("inline")))
607 ;; Unrecognized disposition types are to be treated like
608 ;; attachment according to RFC 2183.
609 (unless (member (car content-disposition) '("inline" "attachment"))
610 (setq content-disposition '("attachment")))
d1be4ec2
KH
611
612 (if parse-only
613 (cond ((string-match "multipart/.*" (car content-type))
614 (setq end (1- end))
615 (save-restriction
616 (let ((header (if show-headers (cons (point-min) end))))
617 (narrow-to-region end (point-max))
618 (rmail-mime-entity content-type
619 content-disposition
620 content-transfer-encoding
621 header nil
622 (rmail-mime-process-multipart
623 content-type content-disposition
624 content-transfer-encoding t)))))
625 ((string-match "message/rfc822" (car content-type))
626 (or show-headers
627 (narrow-to-region end (point-max)))
628 (rmail-mime-process t t))
629 (t
630 (rmail-mime-entity content-type
631 content-disposition
632 content-transfer-encoding
633 nil
634 (cons end (point-max))
635 nil)))
636 ;; Hide headers and handle the part.
637 (save-restriction
638 (cond ((string= (car content-type) "message/rfc822")
639 (narrow-to-region end (point-max)))
640 ((not show-headers)
641 (delete-region (point-min) end)))
642 (rmail-mime-handle content-type content-disposition
643 content-transfer-encoding)))))
644
645(defun rmail-mime-insert-multipart (entity)
646 "Insert MIME-entity ENTITY of multipart type in the current buffer."
647 (let ((subtype (cadr (split-string (car (rmail-mime-entity-type entity))
648 "/")))
649 (disposition (rmail-mime-entity-disposition entity))
650 (header (rmail-mime-entity-header entity))
651 (children (rmail-mime-entity-children entity)))
652 (if header
653 (let ((pos (point)))
654 (or (bolp)
655 (insert "\n"))
656 (insert-buffer-substring rmail-buffer (car header) (cdr header))
657 (rfc2047-decode-region pos (point))
658 (insert "\n")))
659 (cond
660 ((string= subtype "mixed")
661 (dolist (child children)
662 (rmail-mime-insert child '("text/plain") disposition)))
663 ((string= subtype "digest")
664 (dolist (child children)
665 (rmail-mime-insert child '("message/rfc822") disposition)))
666 ((string= subtype "alternative")
667 (let (best-plain-text best-text)
668 (dolist (child children)
669 (if (string= (or (car (rmail-mime-entity-disposition child))
670 (car disposition))
671 "inline")
672 (if (string-match "text/plain"
673 (car (rmail-mime-entity-type child)))
674 (setq best-plain-text child)
675 (if (string-match "text/.*"
676 (car (rmail-mime-entity-type child)))
677 (setq best-text child)))))
678 (if (or best-plain-text best-text)
679 (rmail-mime-insert (or best-plain-text best-text))
680 ;; No child could be handled. Insert all.
681 (dolist (child children)
682 (rmail-mime-insert child nil disposition)))))
683 (t
afde451a 684 ;; Unsupported subtype. Insert all of them.
d1be4ec2 685 (dolist (child children)
afde451a 686 (rmail-mime-insert child))))))
d1be4ec2
KH
687
688(defun rmail-mime-parse ()
689 "Parse the current Rmail message as a MIME message.
690The value is a MIME-entiy object (see `rmail-mime-enty-new')."
691 (save-excursion
692 (goto-char (point-min))
7e116860
KH
693 (condition-case nil
694 (rmail-mime-process nil t)
695 (error nil))))
d1be4ec2
KH
696
697(defun rmail-mime-insert (entity &optional content-type disposition)
698 "Insert a MIME-entity ENTITY in the current buffer.
699
700This function will be called recursively if multiple parts are
701available."
702 (if (rmail-mime-entity-children entity)
703 (rmail-mime-insert-multipart entity)
704 (setq content-type
705 (or (rmail-mime-entity-type entity) content-type))
706 (setq disposition
707 (or (rmail-mime-entity-disposition entity) disposition))
708 (if (and (string= (car disposition) "inline")
709 (string-match "text/.*" (car content-type)))
710 (rmail-mime-insert-text entity)
711 (rmail-mime-insert-bulk entity))))
537ab246 712
2e9075d3
GM
713(define-derived-mode rmail-mime-mode fundamental-mode "RMIME"
714 "Major mode used in `rmail-mime' buffers."
715 (setq font-lock-defaults '(rmail-font-lock-keywords t t nil nil)))
716
73422054 717;;;###autoload
537ab246 718(defun rmail-mime ()
f4ce6150
GM
719 "Process the current Rmail message as a MIME message.
720This creates a temporary \"*RMAIL*\" buffer holding a decoded
2e9075d3 721copy of the message. Inline content-types are handled according to
f4ce6150
GM
722`rmail-mime-media-type-handlers-alist'. By default, this
723displays text and multipart messages, and offers to download
724attachments as specfied by `rmail-mime-attachment-dirs-alist'."
537ab246 725 (interactive)
f4ce6150 726 (let ((data (rmail-apply-in-message rmail-current-message 'buffer-string))
537ab246
BG
727 (buf (get-buffer-create "*RMAIL*")))
728 (set-buffer buf)
f4ce6150 729 (setq buffer-undo-list t)
537ab246 730 (let ((inhibit-read-only t))
d73a0317
GM
731 ;; Decoding the message in fundamental mode for speed, only
732 ;; switching to rmail-mime-mode at the end for display. Eg
733 ;; quoted-printable-decode-region gets very slow otherwise (Bug#4993).
734 (fundamental-mode)
537ab246
BG
735 (erase-buffer)
736 (insert data)
f4ce6150 737 (rmail-mime-show t)
d73a0317 738 (rmail-mime-mode)
f4ce6150 739 (set-buffer-modified-p nil))
537ab246
BG
740 (view-buffer buf)))
741
742(defun rmail-mm-get-boundary-error-message (message type disposition encoding)
743 "Return MESSAGE with more information on the main mime components."
744 (error "%s; type: %s; disposition: %s; encoding: %s"
745 message type disposition encoding))
746
d1be4ec2 747(defun rmail-show-mime ()
7e116860
KH
748 "Function to set in `rmail-show-mime-function' (which see)."
749 (let ((mbox-buf rmail-buffer)
750 (entity (rmail-mime-parse)))
751 (if entity
752 (with-current-buffer rmail-view-buffer
753 (let ((inhibit-read-only t)
754 (rmail-buffer mbox-buf))
755 (erase-buffer)
756 (rmail-mime-insert entity)))
757 ;; Decoding failed. Insert the original message body as is.
758 (let ((region (with-current-buffer mbox-buf
759 (goto-char (point-min))
760 (re-search-forward "^$" nil t)
761 (forward-line 1)
762 (cons (point) (point-max)))))
763 (with-current-buffer rmail-view-buffer
764 (let ((inhibit-read-only t))
765 (erase-buffer)
766 (insert-buffer-substring mbox-buf (car region) (cdr region))))
767 (message "MIME decoding failed")))))
d1be4ec2
KH
768
769(setq rmail-show-mime-function 'rmail-show-mime)
770
771(defun rmail-insert-mime-forwarded-message (forward-buffer)
7e116860 772 "Function to set in `rmail-insert-mime-forwarded-message-function' (which see)."
d1be4ec2
KH
773 (let ((mbox-buf (with-current-buffer forward-buffer rmail-view-buffer)))
774 (save-restriction
775 (narrow-to-region (point) (point))
776 (message-forward-make-body-mime mbox-buf))))
777
778(setq rmail-insert-mime-forwarded-message-function
779 'rmail-insert-mime-forwarded-message)
780
781(defun rmail-insert-mime-resent-message (forward-buffer)
7e116860 782 "Function to set in `rmail-insert-mime-resent-message-function' (which see)."
d1be4ec2
KH
783 (insert-buffer-substring
784 (with-current-buffer forward-buffer rmail-view-buffer))
785 (goto-char (point-min))
786 (when (looking-at "From ")
787 (forward-line 1)
788 (delete-region (point-min) (point))))
789
790(setq rmail-insert-mime-resent-message-function
791 'rmail-insert-mime-resent-message)
792
7e116860
KH
793(defun rmail-search-mime-message (msg regexp)
794 "Function to set in `rmail-search-mime-message-function' (which see)."
795 (save-restriction
796 (narrow-to-region (rmail-msgbeg msg) (rmail-msgend msg))
797 (let ((mbox-buf (current-buffer))
798 (header-end (save-excursion
799 (re-search-forward "^$" nil 'move) (point)))
800 (body-end (point-max))
801 (entity (rmail-mime-parse)))
802 (or
803 ;; At first, just search the headers.
804 (with-temp-buffer
805 (insert-buffer-substring mbox-buf nil header-end)
806 (rfc2047-decode-region (point-min) (point))
807 (goto-char (point-min))
808 (re-search-forward regexp nil t))
809 ;; Next, search the body.
810 (if (and entity
811 (let* ((content-type (rmail-mime-entity-type entity))
812 (charset (cdr (assq 'charset (cdr content-type)))))
813 (or (not (string-match "text/.*" (car content-type)))
814 (and charset
815 (not (string= (downcase charset) "us-ascii"))))))
816 ;; Search the decoded MIME message.
817 (with-temp-buffer
818 (let ((rmail-buffer mbox-buf))
819 (rmail-mime-insert entity))
820 (goto-char (point-min))
821 (re-search-forward regexp nil t))
822 ;; Search the body without decoding.
823 (goto-char header-end)
824 (re-search-forward regexp nil t))))))
825
826(setq rmail-search-mime-message-function 'rmail-search-mime-message)
827
537ab246
BG
828(provide 'rmailmm)
829
35426db4
GM
830;; Local Variables:
831;; generated-autoload-file: "rmail.el"
832;; End:
833
537ab246
BG
834;; arch-tag: 3f2c5e5d-1aef-4512-bc20-fd737c9d5dd9
835;;; rmailmm.el ends here