Improve rmail's MIME handling.
[emacs.git] / lisp / mail / rmailmm.el
blob6dfa92aa93a59b137342ccb41f3ef8a2334acf55
1 ;;; rmailmm.el --- MIME decoding and display stuff for RMAIL
3 ;; Copyright (C) 2006, 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
5 ;; Author: Alexander Pohoyda
6 ;; Alex Schroeder
7 ;; Maintainer: FSF
8 ;; Keywords: mail
10 ;; This file is part of GNU Emacs.
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.
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.
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/>.
25 ;;; Commentary:
27 ;; Essentially based on the design of Alexander Pohoyda's MIME
28 ;; extensions (mime-display.el and mime.el).
30 ;; This file provides two operation modes for viewing a MIME message.
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.
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*".
39 ;; Both operations share the intermediate functions rmail-mime-process
40 ;; and rmail-mime-process-multipart as below.
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 --+
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 --+
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.
68 ;; Todo:
70 ;; Make rmail-mime-media-type-handlers-alist usable in the first
71 ;; operation mode.
72 ;; Handle multipart/alternative in the second operation mode.
73 ;; Offer the option to call external/internal viewers (doc-view, xpdf, etc).
75 ;;; Code:
77 (require 'rmail)
78 (require 'mail-parse)
79 (require 'message)
81 ;;; User options.
83 (defgroup rmail-mime nil
84 "Rmail MIME handling options."
85 :prefix "rmail-mime-"
86 :group 'rmail)
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)
92 ("\\(image\\|audio\\|video\\|application\\)/.*" rmail-mime-bulk-handler))
93 "Functions to handle various content types.
94 This is an alist with elements of the form (REGEXP FUNCTION ...).
95 The first item is a regular expression matching a content-type.
96 The remaining elements are handler functions to run, in order of
97 decreasing preference. These are called until one returns non-nil.
98 Note that this only applies to items with an inline Content-Disposition,
99 all others are handled by `rmail-mime-bulk-handler'."
100 :type '(alist :key-type regexp :value-type (repeat function))
101 :version "23.1"
102 :group 'rmail-mime)
104 (defcustom rmail-mime-attachment-dirs-alist
105 `(("text/.*" "~/Documents")
106 ("image/.*" "~/Pictures")
107 (".*" "~/Desktop" "~" ,temporary-file-directory))
108 "Default directories to save attachments of various types into.
109 This is an alist with elements of the form (REGEXP DIR ...).
110 The first item is a regular expression matching a content-type.
111 The remaining elements are directories, in order of decreasing preference.
112 The first directory that exists is used."
113 :type '(alist :key-type regexp :value-type (repeat directory))
114 :version "23.1"
115 :group 'rmail-mime)
117 (defcustom rmail-mime-show-images 'button
118 "What to do with image attachments that Emacs is capable of displaying.
119 If nil, do nothing special. If `button', add an extra button
120 that when pushed displays the image in the buffer. If a number,
121 automatically show images if they are smaller than that size (in
122 bytes), otherwise add a display button. Anything else means to
123 automatically display the image in the buffer."
124 :type '(choice (const :tag "Add button to view image" button)
125 (const :tag "No special treatment" nil)
126 (number :tag "Show if smaller than certain size")
127 (other :tag "Always show" show))
128 :version "23.2"
129 :group 'rmail-mime)
131 ;;; End of user options.
133 ;;; MIME-entity object
135 (defun rmail-mime-entity (type disposition transfer-encoding
136 header body children)
137 "Retrun a newly created MIME-entity object.
139 A MIME-entity is a vector of 6 elements:
141 [ TYPE DISPOSITION TRANSFER-ENCODING HEADER BODY CHILDREN ]
143 TYPE and DISPOSITION correspond to MIME headers Content-Type: and
144 Cotent-Disposition: respectively, and has this format:
146 \(VALUE (ATTRIBUTE . VALUE) (ATTRIBUTE . VALUE) ...)
148 VALUE is a string and ATTRIBUTE is a symbol.
150 Consider the following header, for example:
152 Content-Type: multipart/mixed;
153 boundary=\"----=_NextPart_000_0104_01C617E4.BDEC4C40\"
155 The corresponding TYPE argument must be:
157 \(\"multipart/mixed\"
158 \(\"boundary\" . \"----=_NextPart_000_0104_01C617E4.BDEC4C40\"))
160 TRANSFER-ENCODING corresponds to MIME header
161 Content-Transfer-Encoding, and is a lowercased string.
163 HEADER and BODY are a cons (BEG . END), where BEG and END specify
164 the region of the corresponding part in RMAIL's data (mbox)
165 buffer. BODY may be nil. In that case, the current buffer is
166 narrowed to the body part.
168 CHILDREN is a list of MIME-entities for a \"multipart\" entity, and
169 nil for the other types."
170 (vector type disposition transfer-encoding header body children))
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))
180 ;;; Buttons
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))
186 (data (button-get button 'data))
187 (mbox-buf rmail-view-buffer)
188 (ofilename filename))
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))
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
201 (set-buffer-file-coding-system 'no-conversion)
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)
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))))))
217 (write-region nil nil filename nil nil nil t))))
219 (define-button-type 'rmail-mime-save 'action 'rmail-mime-save)
221 ;;; Handlers
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))))
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)))))
250 ;; FIXME move to the test/ directory?
251 (defun test-rmail-mime-handler ()
252 "Test of a mail using no MIME parts at all."
253 (let ((mail "To: alex@gnu.org
254 Content-Type: text/plain; charset=koi8-r
255 Content-Transfer-Encoding: 8bit
256 MIME-Version: 1.0
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)))
267 (defun rmail-mime-insert-image (type data)
268 "Insert an image of type TYPE, where DATA is the image data.
269 If DATA is not a string, it is a MIME-entity object."
270 (end-of-line)
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)))
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))))
297 (define-button-type 'rmail-mime-image 'action 'rmail-mime-image)
300 (defun rmail-mime-bulk-handler (content-type
301 content-disposition
302 content-transfer-encoding)
303 "Handle the current buffer as an attachment to download.
304 For images that Emacs is capable of displaying, the behavior
305 depends upon the value of `rmail-mime-show-images'."
306 (rmail-mime-insert-bulk
307 (rmail-mime-entity content-type content-disposition content-transfer-encoding
308 nil nil nil)))
310 (defun rmail-mime-insert-bulk (entity)
311 "Inesrt a MIME-entity ENTITY as an attachment.
312 The optional second arg DATA, if non-nil, is a string containing
313 the attachment data that is already decoded."
314 ;; Find the default directory for this media type.
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
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)))
328 (units '(B kB MB GB))
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
340 (cdr units))
341 (setq size (/ size 1024.0)
342 units (cdr units)))
343 (insert label)
344 (insert-button filename
345 :type 'rmail-mime-save
346 'help-echo "mouse-2, RET: Save attachment"
347 'filename filename
348 'directory (file-name-as-directory directory)
349 'data data)
350 (insert (format " (%.0f%s)" size (car units)))
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))
357 (insert " ")
358 (cond ((or (eq rmail-mime-show-images 'button)
359 (and (numberp rmail-mime-show-images)
360 (>= osize rmail-mime-show-images)))
361 (insert-button "Display"
362 :type 'rmail-mime-image
363 'help-echo "mouse-2, RET: Show image"
364 'image-type type
365 'image-data udata))
367 (rmail-mime-insert-image type udata))))))
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
372 Content-Disposition: attachment; filename=genome.jpeg;
373 modification-date=\"Wed, 12 Feb 1997 16:29:51 -0500\";
374 Content-Description: a complete map of the human genome
375 Content-Transfer-Encoding: base64
377 iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAABGdBTUEAALGPC/xhBQAAAAZQ
378 TFRF////AAAAVcLTfgAAAPZJREFUeNq9ldsOwzAIQ+3//+l1WlvA5ZLsoUiTto4TB+ISoAjy
379 +ITfRBfcAmgRFFeAm+J6uhdKdFhFWUgDkFsK0oUp/9G2//Kj7Jx+5tSKOdBscgUYiKHRS/me
380 WATQdRUvAK0Bnmshmtn79PpaLBbbOZkjKvRnjRZoRswOkG1wFchKew2g9wXVJVZL/m4+B+vv
381 9AxQQR2Q33SgAYJzzVACdAWjAfRYzYFO9n6SLnydtQHSMxYDMAKqZ/8FS/lTK+zuq3CtK64L
382 UDwbgUEAUmk2Zyg101d6PhCDySgAvTvDgKiuOrc4dLxUb7UMnhGIexyI+d6U+ABuNAP4Simx
383 lgAAAABJRU5ErkJggg==
385 (switch-to-buffer (get-buffer-create "*test*"))
386 (erase-buffer)
387 (insert mail)
388 (rmail-mime-show)))
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.
394 The current buffer should be narrowed to the body. CONTENT-TYPE,
395 CONTENT-DISPOSITION, and CONTENT-TRANSFER-ENCODING are the values
396 of the respective parsed headers. See `rmail-mime-handle' for their
397 format."
398 (rmail-mime-process-multipart
399 content-type content-disposition content-transfer-encoding nil))
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.
407 If PARSE-ONLY is nil, modify the current buffer directly for showing
408 the MIME body and return nil.
410 Otherwise, just parse the current buffer and return a list of
411 MIME-entity objects.
413 The other arguments are the same as `rmail-mime-multipart-handler'."
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)))
422 beg end next entities)
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"))
432 (if parse-only
433 (narrow-to-region (match-end 0) (point-max))
434 (delete-region (point-min) (match-end 0))))
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.
444 (cond ((looking-at "--[ \t]*$")
445 (setq next (point-max-marker)))
446 ((looking-at "[ \t]*\n")
447 (setq next (copy-marker (match-end 0) t)))
449 (rmail-mm-get-boundary-error-message
450 "Malformed boundary" content-type content-disposition
451 content-transfer-encoding)))
452 ;; Handle the part.
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)))
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>
467 To: Ned Freed <ned@innosoft.com>
468 Date: Sun, 21 Mar 1993 23:56:48 -0800 (PST)
469 Subject: Sample message
470 MIME-Version: 1.0
471 Content-type: multipart/mixed; boundary=\"simple boundary\"
473 This is the preamble. It is to be ignored, though it
474 is a handy place for composition agents to include an
475 explanatory note to non-MIME conformant readers.
477 --simple boundary
479 This is implicitly typed plain US-ASCII text.
480 It does NOT end with a linebreak.
481 --simple boundary
482 Content-type: text/plain; charset=us-ascii
484 This is explicitly typed plain US-ASCII text.
485 It DOES end with a linebreak.
487 --simple boundary--
489 This 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)))
495 ;;; Main code
497 (defun rmail-mime-handle (content-type
498 content-disposition
499 content-transfer-encoding)
500 "Handle the current buffer as a MIME part.
501 The current buffer should be narrowed to the respective body, and
502 point should be at the beginning of the body.
504 CONTENT-TYPE, CONTENT-DISPOSITION, and CONTENT-TRANSFER-ENCODING
505 are the values of the respective parsed headers. The latter should
506 be downcased. The parsed headers for CONTENT-TYPE and CONTENT-DISPOSITION
507 have the form
509 \(VALUE . ALIST)
511 In other words:
513 \(VALUE (ATTRIBUTE . VALUE) (ATTRIBUTE . VALUE) ...)
515 VALUE is a string and ATTRIBUTE is a symbol.
517 Consider the following header, for example:
519 Content-Type: multipart/mixed;
520 boundary=\"----=_NextPart_000_0104_01C617E4.BDEC4C40\"
522 The parsed header value:
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?
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)
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)))
556 (defun rmail-mime-show (&optional show-headers)
557 "Handle the current buffer as a MIME message.
558 If SHOW-HEADERS is non-nil, then the headers of the current part
559 will shown as usual for a MIME message. The headers are also
560 shown for the content type message/rfc822. This function will be
561 called recursively if multiple parts are available.
563 The current buffer must contain a single message. It will be
564 modified."
565 (rmail-mime-process show-headers nil))
567 (defun rmail-mime-process (show-headers parse-only)
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")))))
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")))
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")))
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))
630 (rmail-mime-entity content-type
631 content-disposition
632 content-transfer-encoding
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)))))
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)))))
684 ;; Unsupported subtype. Insert all as attachment.
685 (dolist (child children)
686 (rmail-mime-insert-bulk child))))))
688 (defun rmail-mime-parse ()
689 "Parse the current Rmail message as a MIME message.
690 The value is a MIME-entiy object (see `rmail-mime-enty-new')."
691 (save-excursion
692 (goto-char (point-min))
693 (rmail-mime-process nil t)))
695 (defun rmail-mime-insert (entity &optional content-type disposition)
696 "Insert a MIME-entity ENTITY in the current buffer.
698 This function will be called recursively if multiple parts are
699 available."
700 (if (rmail-mime-entity-children entity)
701 (rmail-mime-insert-multipart entity)
702 (setq content-type
703 (or (rmail-mime-entity-type entity) content-type))
704 (setq disposition
705 (or (rmail-mime-entity-disposition entity) disposition))
706 (if (and (string= (car disposition) "inline")
707 (string-match "text/.*" (car content-type)))
708 (rmail-mime-insert-text entity)
709 (rmail-mime-insert-bulk entity))))
711 (define-derived-mode rmail-mime-mode fundamental-mode "RMIME"
712 "Major mode used in `rmail-mime' buffers."
713 (setq font-lock-defaults '(rmail-font-lock-keywords t t nil nil)))
715 ;;;###autoload
716 (defun rmail-mime ()
717 "Process the current Rmail message as a MIME message.
718 This creates a temporary \"*RMAIL*\" buffer holding a decoded
719 copy of the message. Inline content-types are handled according to
720 `rmail-mime-media-type-handlers-alist'. By default, this
721 displays text and multipart messages, and offers to download
722 attachments as specfied by `rmail-mime-attachment-dirs-alist'."
723 (interactive)
724 (let ((data (rmail-apply-in-message rmail-current-message 'buffer-string))
725 (buf (get-buffer-create "*RMAIL*")))
726 (set-buffer buf)
727 (setq buffer-undo-list t)
728 (let ((inhibit-read-only t))
729 ;; Decoding the message in fundamental mode for speed, only
730 ;; switching to rmail-mime-mode at the end for display. Eg
731 ;; quoted-printable-decode-region gets very slow otherwise (Bug#4993).
732 (fundamental-mode)
733 (erase-buffer)
734 (insert data)
735 (rmail-mime-show t)
736 (rmail-mime-mode)
737 (set-buffer-modified-p nil))
738 (view-buffer buf)))
740 (defun rmail-mm-get-boundary-error-message (message type disposition encoding)
741 "Return MESSAGE with more information on the main mime components."
742 (error "%s; type: %s; disposition: %s; encoding: %s"
743 message type disposition encoding))
745 (defun rmail-show-mime ()
746 (let ((mbox-buf rmail-buffer))
747 (condition-case nil
748 (let ((entity (rmail-mime-parse)))
749 (with-current-buffer rmail-view-buffer
750 (let ((inhibit-read-only t)
751 (rmail-buffer mbox-buf))
752 (erase-buffer)
753 (rmail-mime-insert entity))))
754 (error
755 ;; Decoding failed. Insert the original message body as is.
756 (let ((region (with-current-buffer mbox-buf
757 (goto-char (point-min))
758 (re-search-forward "^$" nil t)
759 (forward-line 1)
760 (cons (point) (point-max)))))
761 (with-current-buffer rmail-view-buffer
762 (let ((inhibit-read-only t))
763 (erase-buffer)
764 (insert-buffer-substring mbox-buf (car region) (cdr region))))
765 (message "MIME decoding failed"))))))
767 (setq rmail-show-mime-function 'rmail-show-mime)
769 (defun rmail-insert-mime-forwarded-message (forward-buffer)
770 (let ((mbox-buf (with-current-buffer forward-buffer rmail-view-buffer)))
771 (save-restriction
772 (narrow-to-region (point) (point))
773 (message-forward-make-body-mime mbox-buf))))
775 (setq rmail-insert-mime-forwarded-message-function
776 'rmail-insert-mime-forwarded-message)
778 (defun rmail-insert-mime-resent-message (forward-buffer)
779 (insert-buffer-substring
780 (with-current-buffer forward-buffer rmail-view-buffer))
781 (goto-char (point-min))
782 (when (looking-at "From ")
783 (forward-line 1)
784 (delete-region (point-min) (point))))
786 (setq rmail-insert-mime-resent-message-function
787 'rmail-insert-mime-resent-message)
789 (provide 'rmailmm)
791 ;; Local Variables:
792 ;; generated-autoload-file: "rmail.el"
793 ;; End:
795 ;; arch-tag: 3f2c5e5d-1aef-4512-bc20-fd737c9d5dd9
796 ;;; rmailmm.el ends here