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
11 ;; This file is part of GNU Emacs.
13 ;; GNU Emacs is free software: you can redistribute it and/or modify
14 ;; it under the terms of the GNU General Public License as published by
15 ;; the Free Software Foundation, either version 3 of the License, or
16 ;; (at your option) any later version.
18 ;; GNU Emacs is distributed in the hope that it will be useful,
19 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 ;; GNU General Public License for more details.
23 ;; You should have received a copy of the GNU General Public License
24 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
28 ;; Essentially based on the design of Alexander Pohoyda's MIME
29 ;; extensions (mime-display.el and mime.el).
31 ;; This file provides two operation modes for viewing a MIME message.
33 ;; (1) When rmail-enable-mime is non-nil (now it is the default), the
34 ;; function `rmail-show-mime' is automatically called. That function
35 ;; shows a MIME message directly in RMAIL's view buffer.
37 ;; (2) When rmail-enable-mime is nil, the command 'v' (or M-x
38 ;; rmail-mime) shows a MIME message in a new buffer "*RMAIL*".
40 ;; Both operations share the intermediate functions rmail-mime-process
41 ;; and rmail-mime-process-multipart as below.
44 ;; +- rmail-mime-parse
45 ;; | +- rmail-mime-process <--+------------+
47 ;; | + rmail-mime-process-multipart --+
49 ;; + rmail-mime-insert <----------------+
50 ;; +- rmail-mime-insert-text |
51 ;; +- rmail-mime-insert-bulk |
52 ;; +- rmail-mime-insert-multipart --+
55 ;; +- rmail-mime-show <----------------------------------+
56 ;; +- rmail-mime-process |
57 ;; +- rmail-mime-handle |
58 ;; +- rmail-mime-text-handler |
59 ;; +- rmail-mime-bulk-handler |
60 ;; | + rmail-mime-insert-bulk
61 ;; +- rmail-mime-multipart-handler |
62 ;; +- rmail-mime-process-multipart --+
64 ;; In addition, for the case of rmail-enable-mime being non-nil, this
65 ;; file provides two functions rmail-insert-mime-forwarded-message and
66 ;; rmail-insert-mime-resent-message for composing forwarded and resent
67 ;; messages respectively.
71 ;; Make rmail-mime-media-type-handlers-alist usable in the first
73 ;; Handle multipart/alternative in the second operation mode.
74 ;; Offer the option to call external/internal viewers (doc-view, xpdf, etc).
84 (defgroup rmail-mime nil
85 "Rmail MIME handling options."
89 (defcustom rmail-mime-media-type-handlers-alist
90 '(("multipart/.*" rmail-mime-multipart-handler)
91 ("text/.*" rmail-mime-text-handler)
92 ("text/\\(x-\\)?patch" rmail-mime-bulk-handler)
93 ("\\(image\\|audio\\|video\\|application\\)/.*" rmail-mime-bulk-handler))
94 "Functions to handle various content types.
95 This is an alist with elements of the form (REGEXP FUNCTION ...).
96 The first item is a regular expression matching a content-type.
97 The remaining elements are handler functions to run, in order of
98 decreasing preference. These are called until one returns non-nil.
99 Note that this only applies to items with an inline Content-Disposition,
100 all others are handled by `rmail-mime-bulk-handler'."
101 :type '(alist :key-type regexp :value-type (repeat function))
105 (defcustom rmail-mime-attachment-dirs-alist
106 `(("text/.*" "~/Documents")
107 ("image/.*" "~/Pictures")
108 (".*" "~/Desktop" "~" ,temporary-file-directory))
109 "Default directories to save attachments of various types into.
110 This is an alist with elements of the form (REGEXP DIR ...).
111 The first item is a regular expression matching a content-type.
112 The remaining elements are directories, in order of decreasing preference.
113 The first directory that exists is used."
114 :type '(alist :key-type regexp :value-type (repeat directory))
118 (defcustom rmail-mime-show-images 'button
119 "What to do with image attachments that Emacs is capable of displaying.
120 If nil, do nothing special. If `button', add an extra button
121 that when pushed displays the image in the buffer. If a number,
122 automatically show images if they are smaller than that size (in
123 bytes), otherwise add a display button. Anything else means to
124 automatically display the image in the buffer."
125 :type '(choice (const :tag "Add button to view image" button)
126 (const :tag "No special treatment" nil)
127 (number :tag "Show if smaller than certain size")
128 (other :tag "Always show" show))
132 ;;; End of user options.
134 ;;; MIME-entity object
136 (defun rmail-mime-entity (type disposition transfer-encoding
137 header body children)
138 "Retrun a newly created MIME-entity object.
140 A MIME-entity is a vector of 6 elements:
142 [ TYPE DISPOSITION TRANSFER-ENCODING HEADER BODY CHILDREN ]
144 TYPE and DISPOSITION correspond to MIME headers Content-Type: and
145 Cotent-Disposition: respectively, and has this format:
147 \(VALUE (ATTRIBUTE . VALUE) (ATTRIBUTE . VALUE) ...)
149 VALUE is a string and ATTRIBUTE is a symbol.
151 Consider the following header, for example:
153 Content-Type: multipart/mixed;
154 boundary=\"----=_NextPart_000_0104_01C617E4.BDEC4C40\"
156 The corresponding TYPE argument must be:
158 \(\"multipart/mixed\"
159 \(\"boundary\" . \"----=_NextPart_000_0104_01C617E4.BDEC4C40\"))
161 TRANSFER-ENCODING corresponds to MIME header
162 Content-Transfer-Encoding, and is a lowercased string.
164 HEADER and BODY are a cons (BEG . END), where BEG and END specify
165 the region of the corresponding part in RMAIL's data (mbox)
166 buffer. BODY may be nil. In that case, the current buffer is
167 narrowed to the body part.
169 CHILDREN is a list of MIME-entities for a \"multipart\" entity, and
170 nil for the other types."
171 (vector type disposition transfer-encoding header body children))
173 ;; Accessors for a MIME-entity object.
174 (defsubst rmail-mime-entity-type (entity) (aref entity 0))
175 (defsubst rmail-mime-entity-disposition (entity) (aref entity 1))
176 (defsubst rmail-mime-entity-transfer-encoding (entity) (aref entity 2))
177 (defsubst rmail-mime-entity-header (entity) (aref entity 3))
178 (defsubst rmail-mime-entity-body (entity) (aref entity 4))
179 (defsubst rmail-mime-entity-children (entity) (aref entity 5))
183 (defun rmail-mime-save (button)
184 "Save the attachment using info in the BUTTON."
185 (let* ((filename (button-get button 'filename))
186 (directory (button-get button 'directory))
187 (data (button-get button 'data))
188 (mbox-buf rmail-view-buffer)
189 (ofilename filename))
190 (setq filename (expand-file-name
191 (read-file-name (format "Save as (default: %s): " filename)
193 (expand-file-name filename directory))
195 ;; If arg is just a directory, use the default file name, but in
196 ;; that directory (copied from write-file).
197 (if (file-directory-p filename)
198 (setq filename (expand-file-name
199 (file-name-nondirectory ofilename)
200 (file-name-as-directory filename))))
202 (set-buffer-file-coding-system 'no-conversion)
203 ;; Needed e.g. by jka-compr, so if the attachment is a compressed
204 ;; file, the magic signature compares equal with the unibyte
205 ;; signature string recorded in jka-compr-compression-info-list.
206 (set-buffer-multibyte nil)
207 (setq buffer-undo-list t)
210 ;; DATA is a MIME-entity object.
211 (let ((transfer-encoding (rmail-mime-entity-transfer-encoding data))
212 (body (rmail-mime-entity-body data)))
213 (insert-buffer-substring mbox-buf (car body) (cdr body))
214 (cond ((string= transfer-encoding "base64")
215 (ignore-errors (base64-decode-region (point-min) (point-max))))
216 ((string= transfer-encoding "quoted-printable")
217 (quoted-printable-decode-region (point-min) (point-max))))))
218 (write-region nil nil filename nil nil nil t))))
220 (define-button-type 'rmail-mime-save 'action 'rmail-mime-save)
224 (defun rmail-mime-text-handler (content-type
226 content-transfer-encoding)
227 "Handle the current buffer as a plain text MIME part."
228 (let* ((charset (cdr (assq 'charset (cdr content-type))))
229 (coding-system (when charset
230 (intern (downcase charset)))))
231 (when (coding-system-p coding-system)
232 (decode-coding-region (point-min) (point-max) coding-system))))
234 (defun rmail-mime-insert-text (entity)
235 "Insert MIME-entity ENTITY as a plain text MIME part in the current buffer."
236 (let* ((content-type (rmail-mime-entity-type entity))
237 (charset (cdr (assq 'charset (cdr content-type))))
238 (coding-system (if charset (intern (downcase charset))))
239 (transfer-encoding (rmail-mime-entity-transfer-encoding entity))
240 (body (rmail-mime-entity-body entity)))
242 (narrow-to-region (point) (point))
243 (insert-buffer-substring rmail-buffer (car body) (cdr body))
244 (cond ((string= transfer-encoding "base64")
245 (ignore-errors (base64-decode-region (point-min) (point-max))))
246 ((string= transfer-encoding "quoted-printable")
247 (quoted-printable-decode-region (point-min) (point-max))))
248 (if (coding-system-p coding-system)
249 (decode-coding-region (point-min) (point-max) coding-system)))))
251 ;; FIXME move to the test/ directory?
252 (defun test-rmail-mime-handler ()
253 "Test of a mail using no MIME parts at all."
254 (let ((mail "To: alex@gnu.org
255 Content-Type: text/plain; charset=koi8-r
256 Content-Transfer-Encoding: 8bit
259 \372\304\322\301\327\323\324\327\325\312\324\305\41"))
260 (switch-to-buffer (get-buffer-create "*test*"))
262 (set-buffer-multibyte nil)
265 (set-buffer-multibyte t)))
268 (defun rmail-mime-insert-image (type data)
269 "Insert an image of type TYPE, where DATA is the image data.
270 If DATA is not a string, it is a MIME-entity object."
272 (let ((modified (buffer-modified-p)))
274 (unless (stringp data)
275 ;; DATA is a MIME-entity.
276 (let ((transfer-encoding (rmail-mime-entity-transfer-encoding data))
277 (body (rmail-mime-entity-body data))
278 (mbox-buffer rmail-view-buffer))
280 (set-buffer-multibyte nil)
281 (setq buffer-undo-list t)
282 (insert-buffer-substring mbox-buffer (car body) (cdr body))
283 (cond ((string= transfer-encoding "base64")
284 (ignore-errors (base64-decode-region (point-min) (point-max))))
285 ((string= transfer-encoding "quoted-printable")
286 (quoted-printable-decode-region (point-min) (point-max))))
288 (buffer-substring-no-properties (point-min) (point-max))))))
289 (insert-image (create-image data type t))
290 (set-buffer-modified-p modified)))
292 (defun rmail-mime-image (button)
293 "Display the image associated with BUTTON."
294 (let ((inhibit-read-only t))
295 (rmail-mime-insert-image (button-get button 'image-type)
296 (button-get button 'image-data))))
298 (define-button-type 'rmail-mime-image 'action 'rmail-mime-image)
301 (defun rmail-mime-bulk-handler (content-type
303 content-transfer-encoding)
304 "Handle the current buffer as an attachment to download.
305 For images that Emacs is capable of displaying, the behavior
306 depends upon the value of `rmail-mime-show-images'."
307 (rmail-mime-insert-bulk
308 (rmail-mime-entity content-type content-disposition content-transfer-encoding
311 (defun rmail-mime-insert-bulk (entity)
312 "Inesrt a MIME-entity ENTITY as an attachment.
313 The optional second arg DATA, if non-nil, is a string containing
314 the attachment data that is already decoded."
315 ;; Find the default directory for this media type.
316 (let* ((content-type (rmail-mime-entity-type entity))
317 (content-disposition (rmail-mime-entity-disposition entity))
318 (body (rmail-mime-entity-body entity))
319 (directory (catch 'directory
320 (dolist (entry rmail-mime-attachment-dirs-alist)
321 (when (string-match (car entry) (car content-type))
322 (dolist (dir (cdr entry))
323 (when (file-directory-p dir)
324 (throw 'directory dir)))))))
325 (filename (or (cdr (assq 'name (cdr content-type)))
326 (cdr (assq 'filename (cdr content-disposition)))
328 (label (format "\nAttached %s file: " (car content-type)))
329 (units '(B kB MB GB))
330 data udata size osize type)
334 size (- (cdr body) (car body)))
335 (setq data (buffer-string)
336 udata (string-as-unibyte data)
338 (delete-region (point-min) (point-max)))
340 (while (and (> size 1024.0) ; cribbed from gnus-agent-expire-done-message
342 (setq size (/ size 1024.0)
345 (insert-button filename
346 :type 'rmail-mime-save
347 'help-echo "mouse-2, RET: Save attachment"
349 'directory (file-name-as-directory directory)
351 (insert (format " (%.0f%s)" size (car units)))
352 (when (and rmail-mime-show-images
353 (string-match "image/\\(.*\\)" (setq type (car content-type)))
354 (setq type (concat "." (match-string 1 type))
355 type (image-type-from-file-name type))
356 (memq type image-types)
357 (image-type-available-p type))
359 (cond ((or (eq rmail-mime-show-images 'button)
360 (and (numberp rmail-mime-show-images)
361 (>= osize rmail-mime-show-images)))
362 (insert-button "Display"
363 :type 'rmail-mime-image
364 'help-echo "mouse-2, RET: Show image"
368 (rmail-mime-insert-image type udata))))))
370 (defun test-rmail-mime-bulk-handler ()
371 "Test of a mail used as an example in RFC 2183."
372 (let ((mail "Content-Type: image/jpeg
373 Content-Disposition: attachment; filename=genome.jpeg;
374 modification-date=\"Wed, 12 Feb 1997 16:29:51 -0500\";
375 Content-Description: a complete map of the human genome
376 Content-Transfer-Encoding: base64
378 iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAABGdBTUEAALGPC/xhBQAAAAZQ
379 TFRF////AAAAVcLTfgAAAPZJREFUeNq9ldsOwzAIQ+3//+l1WlvA5ZLsoUiTto4TB+ISoAjy
380 +ITfRBfcAmgRFFeAm+J6uhdKdFhFWUgDkFsK0oUp/9G2//Kj7Jx+5tSKOdBscgUYiKHRS/me
381 WATQdRUvAK0Bnmshmtn79PpaLBbbOZkjKvRnjRZoRswOkG1wFchKew2g9wXVJVZL/m4+B+vv
382 9AxQQR2Q33SgAYJzzVACdAWjAfRYzYFO9n6SLnydtQHSMxYDMAKqZ/8FS/lTK+zuq3CtK64L
383 UDwbgUEAUmk2Zyg101d6PhCDySgAvTvDgKiuOrc4dLxUb7UMnhGIexyI+d6U+ABuNAP4Simx
386 (switch-to-buffer (get-buffer-create "*test*"))
391 (defun rmail-mime-multipart-handler (content-type
393 content-transfer-encoding)
394 "Handle the current buffer as a multipart MIME body.
395 The current buffer should be narrowed to the body. CONTENT-TYPE,
396 CONTENT-DISPOSITION, and CONTENT-TRANSFER-ENCODING are the values
397 of the respective parsed headers. See `rmail-mime-handle' for their
399 (rmail-mime-process-multipart
400 content-type content-disposition content-transfer-encoding nil))
402 (defun rmail-mime-process-multipart (content-type
404 content-transfer-encoding
406 "Process the current buffer as a multipart MIME body.
408 If PARSE-ONLY is nil, modify the current buffer directly for showing
409 the MIME body and return nil.
411 Otherwise, just parse the current buffer and return a list of
414 The other arguments are the same as `rmail-mime-multipart-handler'."
415 ;; Some MUAs start boundaries with "--", while it should start
416 ;; with "CRLF--", as defined by RFC 2046:
417 ;; The boundary delimiter MUST occur at the beginning of a line,
418 ;; i.e., following a CRLF, and the initial CRLF is considered to
419 ;; be attached to the boundary delimiter line rather than part
420 ;; of the preceding part.
421 ;; We currently don't handle that.
422 (let ((boundary (cdr (assq 'boundary content-type)))
423 beg end next entities)
425 (rmail-mm-get-boundary-error-message
426 "No boundary defined" content-type content-disposition
427 content-transfer-encoding))
428 (setq boundary (concat "\n--" boundary))
429 ;; Hide the body before the first bodypart
430 (goto-char (point-min))
431 (when (and (search-forward boundary nil t)
432 (looking-at "[ \t]*\n"))
434 (narrow-to-region (match-end 0) (point-max))
435 (delete-region (point-min) (match-end 0))))
436 ;; Loop over all body parts, where beg points at the beginning of
437 ;; the part and end points at the end of the part. next points at
438 ;; the beginning of the next part.
439 (setq beg (point-min))
440 (while (search-forward boundary nil t)
441 (setq end (match-beginning 0))
442 ;; If this is the last boundary according to RFC 2046, hide the
443 ;; epilogue, else hide the boundary only. Use a marker for
444 ;; `next' because `rmail-mime-show' may change the buffer.
445 (cond ((looking-at "--[ \t]*$")
446 (setq next (point-max-marker)))
447 ((looking-at "[ \t]*\n")
448 (setq next (copy-marker (match-end 0) t)))
450 (rmail-mm-get-boundary-error-message
451 "Malformed boundary" content-type content-disposition
452 content-transfer-encoding)))
456 (narrow-to-region beg end)
457 (setq entities (cons (rmail-mime-process nil t) entities)))
458 (delete-region end next)
460 (narrow-to-region beg end)
462 (goto-char (setq beg next)))
463 (nreverse entities)))
465 (defun test-rmail-mime-multipart-handler ()
466 "Test of a mail used as an example in RFC 2046."
467 (let ((mail "From: Nathaniel Borenstein <nsb@bellcore.com>
468 To: Ned Freed <ned@innosoft.com>
469 Date: Sun, 21 Mar 1993 23:56:48 -0800 (PST)
470 Subject: Sample message
472 Content-type: multipart/mixed; boundary=\"simple boundary\"
474 This is the preamble. It is to be ignored, though it
475 is a handy place for composition agents to include an
476 explanatory note to non-MIME conformant readers.
480 This is implicitly typed plain US-ASCII text.
481 It does NOT end with a linebreak.
483 Content-type: text/plain; charset=us-ascii
485 This is explicitly typed plain US-ASCII text.
486 It DOES end with a linebreak.
490 This is the epilogue. It is also to be ignored."))
491 (switch-to-buffer (get-buffer-create "*test*"))
494 (rmail-mime-show t)))
498 (defun rmail-mime-handle (content-type
500 content-transfer-encoding)
501 "Handle the current buffer as a MIME part.
502 The current buffer should be narrowed to the respective body, and
503 point should be at the beginning of the body.
505 CONTENT-TYPE, CONTENT-DISPOSITION, and CONTENT-TRANSFER-ENCODING
506 are the values of the respective parsed headers. The latter should
507 be downcased. The parsed headers for CONTENT-TYPE and CONTENT-DISPOSITION
514 \(VALUE (ATTRIBUTE . VALUE) (ATTRIBUTE . VALUE) ...)
516 VALUE is a string and ATTRIBUTE is a symbol.
518 Consider the following header, for example:
520 Content-Type: multipart/mixed;
521 boundary=\"----=_NextPart_000_0104_01C617E4.BDEC4C40\"
523 The parsed header value:
525 \(\"multipart/mixed\"
526 \(\"boundary\" . \"----=_NextPart_000_0104_01C617E4.BDEC4C40\"))"
527 ;; Handle the content transfer encodings we know. Unknown transfer
528 ;; encodings will be passed on to the various handlers.
529 (cond ((string= content-transfer-encoding "base64")
531 (base64-decode-region (point) (point-max)))
532 (setq content-transfer-encoding nil)))
533 ((string= content-transfer-encoding "quoted-printable")
534 (quoted-printable-decode-region (point) (point-max))
535 (setq content-transfer-encoding nil))
536 ((string= content-transfer-encoding "8bit")
537 ;; FIXME: Is this the correct way?
538 ;; No, of course not, it just means there's no decoding to do.
539 ;; (set-buffer-multibyte nil)
540 (setq content-transfer-encoding nil)
542 ;; Inline stuff requires work. Attachments are handled by the bulk
544 (if (string= "inline" (car content-disposition))
546 (dolist (entry rmail-mime-media-type-handlers-alist)
547 (when (and (string-match (car entry) (car content-type)) (not stop))
549 (setq stop (funcall (cadr entry) content-type
551 content-transfer-encoding))))))
552 ;; Everything else is an attachment.
553 (rmail-mime-bulk-handler content-type
555 content-transfer-encoding)))
557 (defun rmail-mime-show (&optional show-headers)
558 "Handle the current buffer as a MIME message.
559 If SHOW-HEADERS is non-nil, then the headers of the current part
560 will shown as usual for a MIME message. The headers are also
561 shown for the content type message/rfc822. This function will be
562 called recursively if multiple parts are available.
564 The current buffer must contain a single message. It will be
566 (rmail-mime-process show-headers nil))
568 (defun rmail-mime-process (show-headers parse-only)
569 (let ((end (point-min))
571 content-transfer-encoding
573 ;; `point-min' returns the beginning and `end' points at the end
575 (goto-char (point-min))
576 ;; If we're showing a part without headers, then it will start
578 (if (eq (char-after) ?\n)
579 (setq end (1+ (point)))
580 (when (search-forward "\n\n" nil t)
581 (setq end (match-end 0))
583 (narrow-to-region (point-min) end)
584 ;; FIXME: Default disposition of the multipart entities should
587 (mail-fetch-field "Content-Type")
588 content-transfer-encoding
589 (mail-fetch-field "Content-Transfer-Encoding")
591 (mail-fetch-field "Content-Disposition")))))
592 ;; Per RFC 2045, C-T-E is case insensitive (bug#5070), but the others
593 ;; are not completely so. Hopefully mail-header-parse-* DTRT.
594 (if content-transfer-encoding
595 (setq content-transfer-encoding (downcase content-transfer-encoding)))
598 (mail-header-parse-content-type content-type)
599 ;; FIXME: Default "message/rfc822" in a "multipart/digest"
600 ;; according to RFC 2046.
602 (setq content-disposition
603 (if content-disposition
604 (mail-header-parse-content-disposition content-disposition)
605 ;; If none specified, we are free to choose what we deem
606 ;; suitable according to RFC 2183. We like inline.
608 ;; Unrecognized disposition types are to be treated like
609 ;; attachment according to RFC 2183.
610 (unless (member (car content-disposition) '("inline" "attachment"))
611 (setq content-disposition '("attachment")))
614 (cond ((string-match "multipart/.*" (car content-type))
617 (let ((header (if show-headers (cons (point-min) end))))
618 (narrow-to-region end (point-max))
619 (rmail-mime-entity content-type
621 content-transfer-encoding
623 (rmail-mime-process-multipart
624 content-type content-disposition
625 content-transfer-encoding t)))))
626 ((string-match "message/rfc822" (car content-type))
628 (narrow-to-region end (point-max)))
629 (rmail-mime-process t t))
631 (rmail-mime-entity content-type
633 content-transfer-encoding
635 (cons end (point-max))
637 ;; Hide headers and handle the part.
639 (cond ((string= (car content-type) "message/rfc822")
640 (narrow-to-region end (point-max)))
642 (delete-region (point-min) end)))
643 (rmail-mime-handle content-type content-disposition
644 content-transfer-encoding)))))
646 (defun rmail-mime-insert-multipart (entity)
647 "Insert MIME-entity ENTITY of multipart type in the current buffer."
648 (let ((subtype (cadr (split-string (car (rmail-mime-entity-type entity))
650 (disposition (rmail-mime-entity-disposition entity))
651 (header (rmail-mime-entity-header entity))
652 (children (rmail-mime-entity-children entity)))
657 (insert-buffer-substring rmail-buffer (car header) (cdr header))
658 (rfc2047-decode-region pos (point))
661 ((string= subtype "mixed")
662 (dolist (child children)
663 (rmail-mime-insert child '("text/plain") disposition)))
664 ((string= subtype "digest")
665 (dolist (child children)
666 (rmail-mime-insert child '("message/rfc822") disposition)))
667 ((string= subtype "alternative")
668 (let (best-plain-text best-text)
669 (dolist (child children)
670 (if (string= (or (car (rmail-mime-entity-disposition child))
673 (if (string-match "text/plain"
674 (car (rmail-mime-entity-type child)))
675 (setq best-plain-text child)
676 (if (string-match "text/.*"
677 (car (rmail-mime-entity-type child)))
678 (setq best-text child)))))
679 (if (or best-plain-text best-text)
680 (rmail-mime-insert (or best-plain-text best-text))
681 ;; No child could be handled. Insert all.
682 (dolist (child children)
683 (rmail-mime-insert child nil disposition)))))
685 ;; Unsupported subtype. Insert all of them.
686 (dolist (child children)
687 (rmail-mime-insert child))))))
689 (defun rmail-mime-parse ()
690 "Parse the current Rmail message as a MIME message.
691 The value is a MIME-entiy object (see `rmail-mime-enty-new')."
693 (goto-char (point-min))
695 (rmail-mime-process nil t)
698 (defun rmail-mime-insert (entity &optional content-type disposition)
699 "Insert a MIME-entity ENTITY in the current buffer.
701 This function will be called recursively if multiple parts are
703 (if (rmail-mime-entity-children entity)
704 (rmail-mime-insert-multipart entity)
706 (or (rmail-mime-entity-type entity) content-type))
708 (or (rmail-mime-entity-disposition entity) disposition))
709 (if (and (string= (car disposition) "inline")
710 (string-match "text/.*" (car content-type)))
711 (rmail-mime-insert-text entity)
712 (rmail-mime-insert-bulk entity))))
714 (define-derived-mode rmail-mime-mode fundamental-mode "RMIME"
715 "Major mode used in `rmail-mime' buffers."
716 (setq font-lock-defaults '(rmail-font-lock-keywords t t nil nil)))
720 "Process the current Rmail message as a MIME message.
721 This creates a temporary \"*RMAIL*\" buffer holding a decoded
722 copy of the message. Inline content-types are handled according to
723 `rmail-mime-media-type-handlers-alist'. By default, this
724 displays text and multipart messages, and offers to download
725 attachments as specfied by `rmail-mime-attachment-dirs-alist'."
727 (let ((data (rmail-apply-in-message rmail-current-message 'buffer-string))
728 (buf (get-buffer-create "*RMAIL*")))
730 (setq buffer-undo-list t)
731 (let ((inhibit-read-only t))
732 ;; Decoding the message in fundamental mode for speed, only
733 ;; switching to rmail-mime-mode at the end for display. Eg
734 ;; quoted-printable-decode-region gets very slow otherwise (Bug#4993).
740 (set-buffer-modified-p nil))
743 (defun rmail-mm-get-boundary-error-message (message type disposition encoding)
744 "Return MESSAGE with more information on the main mime components."
745 (error "%s; type: %s; disposition: %s; encoding: %s"
746 message type disposition encoding))
748 (defun rmail-show-mime ()
749 "Function to set in `rmail-show-mime-function' (which see)."
750 (let ((mbox-buf rmail-buffer)
751 (entity (rmail-mime-parse)))
753 (with-current-buffer rmail-view-buffer
754 (let ((inhibit-read-only t)
755 (rmail-buffer mbox-buf))
757 (rmail-mime-insert entity)))
758 ;; Decoding failed. Insert the original message body as is.
759 (let ((region (with-current-buffer mbox-buf
760 (goto-char (point-min))
761 (re-search-forward "^$" nil t)
763 (cons (point) (point-max)))))
764 (with-current-buffer rmail-view-buffer
765 (let ((inhibit-read-only t))
767 (insert-buffer-substring mbox-buf (car region) (cdr region))))
768 (message "MIME decoding failed")))))
770 (setq rmail-show-mime-function 'rmail-show-mime)
772 (defun rmail-insert-mime-forwarded-message (forward-buffer)
773 "Function to set in `rmail-insert-mime-forwarded-message-function' (which see)."
774 (let ((mbox-buf (with-current-buffer forward-buffer rmail-view-buffer)))
776 (narrow-to-region (point) (point))
777 (message-forward-make-body-mime mbox-buf))))
779 (setq rmail-insert-mime-forwarded-message-function
780 'rmail-insert-mime-forwarded-message)
782 (defun rmail-insert-mime-resent-message (forward-buffer)
783 "Function to set in `rmail-insert-mime-resent-message-function' (which see)."
784 (insert-buffer-substring
785 (with-current-buffer forward-buffer rmail-view-buffer))
786 (goto-char (point-min))
787 (when (looking-at "From ")
789 (delete-region (point-min) (point))))
791 (setq rmail-insert-mime-resent-message-function
792 'rmail-insert-mime-resent-message)
794 (defun rmail-search-mime-message (msg regexp)
795 "Function to set in `rmail-search-mime-message-function' (which see)."
797 (narrow-to-region (rmail-msgbeg msg) (rmail-msgend msg))
798 (let ((mbox-buf (current-buffer))
799 (header-end (save-excursion
800 (re-search-forward "^$" nil 'move) (point)))
801 (body-end (point-max))
802 (entity (rmail-mime-parse)))
804 ;; At first, just search the headers.
806 (insert-buffer-substring mbox-buf nil header-end)
807 (rfc2047-decode-region (point-min) (point))
808 (goto-char (point-min))
809 (re-search-forward regexp nil t))
810 ;; Next, search the body.
812 (let* ((content-type (rmail-mime-entity-type entity))
813 (charset (cdr (assq 'charset (cdr content-type)))))
814 (or (not (string-match "text/.*" (car content-type)))
816 (not (string= (downcase charset) "us-ascii"))))))
817 ;; Search the decoded MIME message.
819 (let ((rmail-buffer mbox-buf))
820 (rmail-mime-insert entity))
821 (goto-char (point-min))
822 (re-search-forward regexp nil t))
823 ;; Search the body without decoding.
824 (goto-char header-end)
825 (re-search-forward regexp nil t))))))
827 (setq rmail-search-mime-message-function 'rmail-search-mime-message)
832 ;; generated-autoload-file: "rmail.el"
835 ;; arch-tag: 3f2c5e5d-1aef-4512-bc20-fd737c9d5dd9
836 ;;; rmailmm.el ends here