Convert consecutive FSF copyright years to ranges.
[bpt/emacs.git] / lisp / mail / rmailmm.el
CommitLineData
537ab246
BG
1;;; rmailmm.el --- MIME decoding and display stuff for RMAIL
2
73b0cd50 3;; Copyright (C) 2006-2011 Free Software Foundation, Inc.
537ab246 4
6ea97e7e
GM
5;; Author: Alexander Pohoyda
6;; Alex Schroeder
537ab246
BG
7;; Maintainer: FSF
8;; Keywords: mail
bd78fa1d 9;; Package: rmail
537ab246
BG
10
11;; This file is part of GNU Emacs.
12
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.
17
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.
22
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/>.
25
26;;; Commentary:
27
28;; Essentially based on the design of Alexander Pohoyda's MIME
73422054 29;; extensions (mime-display.el and mime.el).
d1be4ec2
KH
30
31;; This file provides two operation modes for viewing a MIME message.
32
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.
36
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*".
39
40;; Both operations share the intermediate functions rmail-mime-process
41;; and rmail-mime-process-multipart as below.
42
43;; rmail-show-mime
44;; +- rmail-mime-parse
45;; | +- rmail-mime-process <--+------------+
46;; | | +---------+ |
47;; | + rmail-mime-process-multipart --+
48;; |
49;; + rmail-mime-insert <----------------+
50;; +- rmail-mime-insert-text |
51;; +- rmail-mime-insert-bulk |
52;; +- rmail-mime-insert-multipart --+
53;;
54;; rmail-mime
55;; +- rmail-mime-show <----------------------------------+
7c420169 56;; +- rmail-mime-process |
d1be4ec2
KH
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 --+
63
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.
537ab246 68
2e9075d3
GM
69;; Todo:
70
d1be4ec2
KH
71;; Make rmail-mime-media-type-handlers-alist usable in the first
72;; operation mode.
73;; Handle multipart/alternative in the second operation mode.
a92cdd49 74;; Offer the option to call external/internal viewers (doc-view, xpdf, etc).
2e9075d3 75
537ab246
BG
76;;; Code:
77
78(require 'rmail)
79(require 'mail-parse)
d1be4ec2 80(require 'message)
537ab246 81
f4ce6150 82;;; User options.
537ab246 83
e8652dd9
GM
84(defgroup rmail-mime nil
85 "Rmail MIME handling options."
86 :prefix "rmail-mime-"
87 :group 'rmail)
88
537ab246
BG
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)
e8652dd9 93 ("\\(image\\|audio\\|video\\|application\\)/.*" rmail-mime-bulk-handler))
f4ce6150 94 "Functions to handle various content types.
c8644de0
GM
95This is an alist with elements of the form (REGEXP FUNCTION ...).
96The first item is a regular expression matching a content-type.
97The remaining elements are handler functions to run, in order of
2e9075d3
GM
98decreasing preference. These are called until one returns non-nil.
99Note that this only applies to items with an inline Content-Disposition,
186f7f0b
KH
100all others are handled by `rmail-mime-bulk-handler'.
101Note also that this alist is ignored when the variable
102`rmail-enable-mime' is non-nil."
c8644de0 103 :type '(alist :key-type regexp :value-type (repeat function))
e8652dd9
GM
104 :version "23.1"
105 :group 'rmail-mime)
537ab246
BG
106
107(defcustom rmail-mime-attachment-dirs-alist
f265b4de 108 `(("text/.*" "~/Documents")
537ab246 109 ("image/.*" "~/Pictures")
f265b4de 110 (".*" "~/Desktop" "~" ,temporary-file-directory))
f4ce6150
GM
111 "Default directories to save attachments of various types into.
112This is an alist with elements of the form (REGEXP DIR ...).
113The first item is a regular expression matching a content-type.
114The remaining elements are directories, in order of decreasing preference.
115The first directory that exists is used."
116 :type '(alist :key-type regexp :value-type (repeat directory))
117 :version "23.1"
e8652dd9
GM
118 :group 'rmail-mime)
119
120(defcustom rmail-mime-show-images 'button
121 "What to do with image attachments that Emacs is capable of displaying.
122If nil, do nothing special. If `button', add an extra button
a92cdd49
GM
123that when pushed displays the image in the buffer. If a number,
124automatically show images if they are smaller than that size (in
125bytes), otherwise add a display button. Anything else means to
126automatically display the image in the buffer."
e8652dd9
GM
127 :type '(choice (const :tag "Add button to view image" button)
128 (const :tag "No special treatment" nil)
a92cdd49 129 (number :tag "Show if smaller than certain size")
e8652dd9
GM
130 (other :tag "Always show" show))
131 :version "23.2"
132 :group 'rmail-mime)
537ab246 133
f4ce6150
GM
134;;; End of user options.
135
186f7f0b
KH
136;;; Global variables that always have let-binding when referred.
137
138(defvar rmail-mime-mbox-buffer nil
139 "Buffer containing the mbox data.
140The value is usually nil, and bound to a proper value while
141processing MIME.")
142
143(defvar rmail-mime-view-buffer nil
144 "Buffer showing a message.
145The value is usually nil, and bound to a proper value while
146processing MIME.")
147
148(defvar rmail-mime-coding-system nil
149 "The first coding-system used for decoding a MIME entity.
150The value is usually nil, and bound to non-nil while inserting
151MIME entities.")
152
d1be4ec2
KH
153;;; MIME-entity object
154
155(defun rmail-mime-entity (type disposition transfer-encoding
186f7f0b
KH
156 display header tagline body children handler)
157 "Retrun a newly created MIME-entity object from arguments.
d1be4ec2 158
186f7f0b 159A MIME-entity is a vector of 9 elements:
d1be4ec2 160
186f7f0b
KH
161 [TYPE DISPOSITION TRANSFER-ENCODING DISPLAY HEADER TAGLINE BODY
162 CHILDREN HANDLER]
7c420169 163
186f7f0b
KH
164TYPE and DISPOSITION correspond to MIME headers Content-Type and
165Cotent-Disposition respectively, and has this format:
d1be4ec2
KH
166
167 \(VALUE (ATTRIBUTE . VALUE) (ATTRIBUTE . VALUE) ...)
168
169VALUE is a string and ATTRIBUTE is a symbol.
170
171Consider the following header, for example:
172
173Content-Type: multipart/mixed;
174 boundary=\"----=_NextPart_000_0104_01C617E4.BDEC4C40\"
175
176The corresponding TYPE argument must be:
177
178\(\"multipart/mixed\"
179 \(\"boundary\" . \"----=_NextPart_000_0104_01C617E4.BDEC4C40\"))
180
181TRANSFER-ENCODING corresponds to MIME header
182Content-Transfer-Encoding, and is a lowercased string.
183
186f7f0b
KH
184DISPLAY is a vector [CURRENT NEW], where CURRENT indicates how
185the header, tagline, and body of the entity are displayed now,
186and NEW indicates how their displaying should be updated.
187Both elements are vector [HEADER-DISPLAY TAGLINE-DISPLAY BODY-DISPLAY],
188where each element is a symbol for the corresponding item that
189has these values:
190 nil: not displayed
191 t: displayed by the decoded presentation form
192 raw: displayed by the raw MIME data (for the header and body only)
193
194HEADER and BODY are vectors [BEG END DISPLAY-FLAG], where BEG and
195END specify the region of the header or body lines in RMAIL's
196data (mbox) buffer, and DISPLAY-FLAG non-nil means that the
197header or body is, by default, displayed by the decoded
198presentation form.
199
200TAGLINE is a vector [TAG BULK-DATA DISPLAY-FLAG], where TAG is a
201string indicating the depth and index number of the entity,
202BULK-DATA is a cons (SIZE . TYPE) indicating the size and type of
203an attached data, DISPLAY-FLAG non-nil means that the tagline is,
204by default, displayed.
205
206CHILDREN is a list of child MIME-entities. A \"multipart/*\"
207entity have one or more children. A \"message/rfc822\" entity
208has just one child. Any other entity has no child.
209
210HANDLER is a function to insert the entity according to DISPLAY.
211It is called with one argument ENTITY."
212 (vector type disposition transfer-encoding
213 display header tagline body children handler))
d1be4ec2
KH
214
215;; Accessors for a MIME-entity object.
216(defsubst rmail-mime-entity-type (entity) (aref entity 0))
217(defsubst rmail-mime-entity-disposition (entity) (aref entity 1))
218(defsubst rmail-mime-entity-transfer-encoding (entity) (aref entity 2))
186f7f0b
KH
219(defsubst rmail-mime-entity-display (entity) (aref entity 3))
220(defsubst rmail-mime-entity-header (entity) (aref entity 4))
221(defsubst rmail-mime-entity-tagline (entity) (aref entity 5))
222(defsubst rmail-mime-entity-body (entity) (aref entity 6))
223(defsubst rmail-mime-entity-children (entity) (aref entity 7))
224(defsubst rmail-mime-entity-handler (entity) (aref entity 8))
225
226(defsubst rmail-mime-message-p ()
227 "Non-nil if and only if the current message is a MIME."
228 (or (get-text-property (point) 'rmail-mime-entity)
229 (get-text-property (point-min) 'rmail-mime-entity)))
f4ce6150 230
537ab246
BG
231;;; Buttons
232
233(defun rmail-mime-save (button)
234 "Save the attachment using info in the BUTTON."
186f7f0b
KH
235 (let* ((rmail-mime-mbox-buffer rmail-view-buffer)
236 (filename (button-get button 'filename))
537ab246 237 (directory (button-get button 'directory))
fe6793d4
GM
238 (data (button-get button 'data))
239 (ofilename filename))
537ab246
BG
240 (setq filename (expand-file-name
241 (read-file-name (format "Save as (default: %s): " filename)
242 directory
243 (expand-file-name filename directory))
244 directory))
fe6793d4
GM
245 ;; If arg is just a directory, use the default file name, but in
246 ;; that directory (copied from write-file).
247 (if (file-directory-p filename)
248 (setq filename (expand-file-name
249 (file-name-nondirectory ofilename)
250 (file-name-as-directory filename))))
251 (with-temp-buffer
537ab246 252 (set-buffer-file-coding-system 'no-conversion)
134a027f
EZ
253 ;; Needed e.g. by jka-compr, so if the attachment is a compressed
254 ;; file, the magic signature compares equal with the unibyte
255 ;; signature string recorded in jka-compr-compression-info-list.
256 (set-buffer-multibyte nil)
d1be4ec2
KH
257 (setq buffer-undo-list t)
258 (if (stringp data)
259 (insert data)
260 ;; DATA is a MIME-entity object.
261 (let ((transfer-encoding (rmail-mime-entity-transfer-encoding data))
262 (body (rmail-mime-entity-body data)))
186f7f0b
KH
263 (insert-buffer-substring rmail-mime-mbox-buffer
264 (aref body 0) (aref body 1))
d1be4ec2
KH
265 (cond ((string= transfer-encoding "base64")
266 (ignore-errors (base64-decode-region (point-min) (point-max))))
267 ((string= transfer-encoding "quoted-printable")
268 (quoted-printable-decode-region (point-min) (point-max))))))
fe6793d4 269 (write-region nil nil filename nil nil nil t))))
537ab246 270
fe6793d4 271(define-button-type 'rmail-mime-save 'action 'rmail-mime-save)
537ab246 272
186f7f0b
KH
273(defun rmail-mime-entity-segment (pos &optional entity)
274 "Return a vector describing the displayed region of a MIME-entity at POS.
275Optional 2nd argument ENTITY is the MIME-entity at POS.
276The value is a vector [ INDEX HEADER TAGLINE BODY END], where
e7ca0062 277 INDEX: index into the returned vector indicating where POS is (1..3).
186f7f0b
KH
278 HEADER: the position of the beginning of a header
279 TAGLINE: the position of the beginning of a tagline
280 BODY: the position of the beginning of a body
e7ca0062 281 END: the position of the end of the entity."
186f7f0b
KH
282 (save-excursion
283 (or entity
284 (setq entity (get-text-property pos 'rmail-mime-entity)))
285 (if (not entity)
286 (vector 1 (point) (point) (point) (point))
287 (let ((current (aref (rmail-mime-entity-display entity) 0))
288 (beg (if (and (> pos (point-min))
289 (eq (get-text-property (1- pos) 'rmail-mime-entity)
290 entity))
291 (previous-single-property-change pos 'rmail-mime-entity
292 nil (point-min))
293 pos))
294 (index 1)
295 tagline-beg body-beg end)
296 (goto-char beg)
297 (if (aref current 0)
298 (search-forward "\n\n" nil t))
299 (setq tagline-beg (point))
300 (if (>= pos tagline-beg)
301 (setq index 2))
302 (if (aref current 1)
303 (forward-line 1))
304 (setq body-beg (point))
305 (if (>= pos body-beg)
306 (setq index 3))
307 (if (aref current 2)
308 (let ((tag (aref (rmail-mime-entity-tagline entity) 0))
309 tag2)
310 (setq end (next-single-property-change beg 'rmail-mime-entity
311 nil (point-max)))
312 (while (and (< end (point-max))
313 (setq entity (get-text-property end 'rmail-mime-entity)
314 tag2 (aref (rmail-mime-entity-tagline entity) 0))
315 (and (> (length tag2) 0)
316 (eq (string-match tag tag2) 0)))
317 (setq end (next-single-property-change end 'rmail-mime-entity
318 nil (point-max)))))
319 (setq end body-beg))
320 (vector index beg tagline-beg body-beg end)))))
321
186f7f0b
KH
322(defun rmail-mime-shown-mode (entity)
323 "Make MIME-entity ENTITY displayed by the default way."
324 (let ((new (aref (rmail-mime-entity-display entity) 1)))
325 (aset new 0 (aref (rmail-mime-entity-header entity) 2))
326 (aset new 1 (aref (rmail-mime-entity-tagline entity) 2))
e7ca0062 327 (aset new 2 (aref (rmail-mime-entity-body entity) 2)))
186f7f0b 328 (dolist (child (rmail-mime-entity-children entity))
e7ca0062 329 (rmail-mime-shown-mode child)))
d52969e8 330
e7ca0062
KH
331(defun rmail-mime-hidden-mode (entity)
332 "Make MIME-entity ENTITY displayed in the hidden mode."
333 (let ((new (aref (rmail-mime-entity-display entity) 1)))
334 (aset new 0 nil)
335 (aset new 1 t)
336 (aset new 2 nil))
186f7f0b 337 (dolist (child (rmail-mime-entity-children entity))
e7ca0062 338 (rmail-mime-hidden-mode child)))
186f7f0b
KH
339
340(defun rmail-mime-raw-mode (entity)
341 "Make MIME-entity ENTITY displayed in the raw mode."
342 (let ((new (aref (rmail-mime-entity-display entity) 1)))
343 (aset new 0 'raw)
344 (aset new 1 nil)
e7ca0062
KH
345 (aset new 2 'raw))
346 (dolist (child (rmail-mime-entity-children entity))
347 (rmail-mime-raw-mode child)))
186f7f0b
KH
348
349(defun rmail-mime-toggle-raw (entity)
350 "Toggle on and off the raw display mode of MIME-entity ENTITY."
351 (let* ((pos (if (eobp) (1- (point-max)) (point)))
352 (entity (get-text-property pos 'rmail-mime-entity))
353 (current (aref (rmail-mime-entity-display entity) 0))
354 (segment (rmail-mime-entity-segment pos entity)))
355 (if (not (eq (aref current 0) 'raw))
356 ;; Enter the raw mode.
357 (rmail-mime-raw-mode entity)
358 ;; Enter the shown mode.
359 (rmail-mime-shown-mode entity))
360 (let ((inhibit-read-only t)
361 (modified (buffer-modified-p)))
362 (save-excursion
363 (goto-char (aref segment 1))
364 (rmail-mime-insert entity)
365 (restore-buffer-modified-p modified)))))
366
367(defun rmail-mime-toggle-hidden ()
e7ca0062 368 "Hide or show the body of MIME-entity at point."
186f7f0b
KH
369 (interactive)
370 (when (rmail-mime-message-p)
371 (let* ((rmail-mime-mbox-buffer rmail-view-buffer)
372 (rmail-mime-view-buffer (current-buffer))
373 (pos (if (eobp) (1- (point-max)) (point)))
374 (entity (get-text-property pos 'rmail-mime-entity))
375 (current (aref (rmail-mime-entity-display entity) 0))
376 (segment (rmail-mime-entity-segment pos entity)))
377 (if (aref current 2)
378 ;; Enter the hidden mode.
379 (progn
380 ;; If point is in the body part, move it to the tagline
e7ca0062 381 ;; (or the header if tagline is not displayed).
186f7f0b
KH
382 (if (= (aref segment 0) 3)
383 (goto-char (aref segment 2)))
e7ca0062 384 (rmail-mime-hidden-mode entity)
186f7f0b
KH
385 ;; If the current entity is the topmost one, display the
386 ;; header.
387 (if (and rmail-mime-mbox-buffer (= (aref segment 1) (point-min)))
388 (let ((new (aref (rmail-mime-entity-display entity) 1)))
389 (aset new 0 t))))
390 ;; Enter the shown mode.
e7ca0062
KH
391 (rmail-mime-shown-mode entity)
392 ;; Force this body shown.
393 (aset (aref (rmail-mime-entity-display entity) 1) 2 t))
186f7f0b
KH
394 (let ((inhibit-read-only t)
395 (modified (buffer-modified-p))
396 (rmail-mime-mbox-buffer rmail-view-buffer)
397 (rmail-mime-view-buffer rmail-buffer))
398 (save-excursion
399 (goto-char (aref segment 1))
400 (rmail-mime-insert entity)
401 (restore-buffer-modified-p modified))))))
402
e7ca0062
KH
403(define-key rmail-mode-map "\t" 'forward-button)
404(define-key rmail-mode-map [backtab] 'backward-button)
186f7f0b
KH
405(define-key rmail-mode-map "\r" 'rmail-mime-toggle-hidden)
406
537ab246
BG
407;;; Handlers
408
186f7f0b
KH
409(defun rmail-mime-insert-tagline (entity &rest item-list)
410 "Insert a tag line for MIME-entity ENTITY.
411ITEM-LIST is a list of strings or button-elements (list) to be added
412to the tag line."
413 (insert "[")
414 (let ((tag (aref (rmail-mime-entity-tagline entity) 0)))
415 (if (> (length tag) 0) (insert (substring tag 1) ":")))
e7ca0062
KH
416 (insert (car (rmail-mime-entity-type entity)) " ")
417 (insert-button (let ((new (aref (rmail-mime-entity-display entity) 1)))
418 (if (aref new 2) "Hide" "Show"))
419 :type 'rmail-mime-toggle
420 'help-echo "mouse-2, RET: Toggle show/hide")
186f7f0b
KH
421 (dolist (item item-list)
422 (when item
423 (if (stringp item)
424 (insert item)
425 (apply 'insert-button item))))
426 (insert "]\n"))
7c420169 427
e7ca0062
KH
428(defun rmail-mime-update-tagline (entity)
429 "Update the current tag line for MIME-entity ENTITY."
430 (let ((inhibit-read-only t)
431 (modified (buffer-modified-p))
432 ;; If we are going to show the body, the new button label is
433 ;; "Hide". Otherwise, it's "Show".
434 (label (if (aref (aref (rmail-mime-entity-display entity) 1) 2) "Hide"
435 "Show"))
436 (button (next-button (point))))
437 ;; Go to the second character of the button "Show" or "Hide".
438 (goto-char (1+ (button-start button)))
439 (setq button (button-at (point)))
440 (save-excursion
441 (insert label)
442 (delete-region (point) (button-end button)))
443 (delete-region (button-start button) (point))
444 (put-text-property (point) (button-end button) 'rmail-mime-entity entity)
445 (restore-buffer-modified-p modified)
446 (forward-line 1)))
447
186f7f0b
KH
448(defun rmail-mime-insert-header (header)
449 "Decode and insert a MIME-entity header HEADER in the current buffer.
450HEADER is a vector [BEG END DEFAULT-STATUS].
451See `rmail-mime-entity' for the detail."
452 (let ((pos (point))
453 (last-coding-system-used nil))
454 (save-restriction
455 (narrow-to-region pos pos)
456 (with-current-buffer rmail-mime-mbox-buffer
457 (let ((rmail-buffer rmail-mime-mbox-buffer)
458 (rmail-view-buffer rmail-mime-view-buffer))
459 (save-excursion
460 (goto-char (aref header 0))
461 (rmail-copy-headers (point) (aref header 1)))))
462 (rfc2047-decode-region pos (point))
463 (if (and last-coding-system-used (not rmail-mime-coding-system))
1a6a03e4 464 (setq rmail-mime-coding-system (cons last-coding-system-used nil)))
186f7f0b
KH
465 (goto-char (point-min))
466 (rmail-highlight-headers)
467 (goto-char (point-max))
468 (insert "\n"))))
469
1a6a03e4
KH
470(defun rmail-mime-find-header-encoding (header)
471 "Retun the last coding system used to decode HEADER.
472HEADER is a header component of a MIME-entity object (see
473`rmail-mime-entity')."
474 (with-temp-buffer
475 (let ((last-coding-system-used nil))
476 (with-current-buffer rmail-mime-mbox-buffer
477 (let ((rmail-buffer rmail-mime-mbox-buffer)
478 (rmail-view-buffer rmail-mime-view-buffer))
479 (save-excursion
480 (goto-char (aref header 0))
481 (rmail-copy-headers (point) (aref header 1)))))
482 (rfc2047-decode-region (point-min) (point-max))
483 last-coding-system-used)))
484
537ab246
BG
485(defun rmail-mime-text-handler (content-type
486 content-disposition
487 content-transfer-encoding)
488 "Handle the current buffer as a plain text MIME part."
186f7f0b
KH
489 (rmail-mime-insert-text
490 (rmail-mime-entity content-type content-disposition
491 content-transfer-encoding
492 (vector (vector nil nil nil) (vector nil nil t))
493 (vector nil nil nil) (vector "" (cons nil nil) t)
494 (vector nil nil nil) nil 'rmail-mime-insert-text))
495 t)
496
497(defun rmail-mime-insert-decoded-text (entity)
498 "Decode and insert the text body of MIME-entity ENTITY."
d1be4ec2
KH
499 (let* ((content-type (rmail-mime-entity-type entity))
500 (charset (cdr (assq 'charset (cdr content-type))))
186f7f0b
KH
501 (coding-system (if charset
502 (coding-system-from-name charset)))
503 (body (rmail-mime-entity-body entity))
504 (pos (point)))
505 (or (and coding-system (coding-system-p coding-system))
506 (setq coding-system 'undecided))
507 (if (stringp (aref body 0))
508 (insert (aref body 0))
509 (let ((transfer-encoding (rmail-mime-entity-transfer-encoding entity)))
510 (insert-buffer-substring rmail-mime-mbox-buffer
511 (aref body 0) (aref body 1))
512 (cond ((string= transfer-encoding "base64")
513 (ignore-errors (base64-decode-region pos (point))))
514 ((string= transfer-encoding "quoted-printable")
515 (quoted-printable-decode-region pos (point))))))
516 (decode-coding-region pos (point) coding-system)
1a6a03e4 517 (if (or (not rmail-mime-coding-system) (consp rmail-mime-coding-system))
186f7f0b
KH
518 (setq rmail-mime-coding-system coding-system))
519 (or (bolp) (insert "\n"))))
520
521(defun rmail-mime-insert-text (entity)
522 "Presentation handler for a plain text MIME entity."
523 (let ((current (aref (rmail-mime-entity-display entity) 0))
524 (new (aref (rmail-mime-entity-display entity) 1))
525 (header (rmail-mime-entity-header entity))
526 (tagline (rmail-mime-entity-tagline entity))
527 (body (rmail-mime-entity-body entity))
528 (beg (point))
529 (segment (rmail-mime-entity-segment (point) entity)))
530
531 (or (integerp (aref body 0))
532 (let ((data (buffer-string)))
533 (aset body 0 data)
534 (delete-region (point-min) (point-max))))
535
536 ;; header
537 (if (eq (aref current 0) (aref new 0))
538 (goto-char (aref segment 2))
539 (if (aref current 0)
540 (delete-char (- (aref segment 2) (aref segment 1))))
541 (if (aref new 0)
542 (rmail-mime-insert-header header)))
543 ;; tagline
544 (if (eq (aref current 1) (aref new 1))
e7ca0062
KH
545 (if (or (not (aref current 1))
546 (eq (aref current 2) (aref new 2)))
547 (forward-char (- (aref segment 3) (aref segment 2)))
548 (rmail-mime-update-tagline entity))
186f7f0b
KH
549 (if (aref current 1)
550 (delete-char (- (aref segment 3) (aref segment 2))))
551 (if (aref new 1)
552 (rmail-mime-insert-tagline entity)))
553 ;; body
554 (if (eq (aref current 2) (aref new 2))
555 (forward-char (- (aref segment 4) (aref segment 3)))
556 (if (aref current 2)
557 (delete-char (- (aref segment 4) (aref segment 3))))
558 (if (aref new 2)
559 (rmail-mime-insert-decoded-text entity)))
560 (put-text-property beg (point) 'rmail-mime-entity entity)))
d1be4ec2 561
f4ce6150 562;; FIXME move to the test/ directory?
537ab246
BG
563(defun test-rmail-mime-handler ()
564 "Test of a mail using no MIME parts at all."
565 (let ((mail "To: alex@gnu.org
566Content-Type: text/plain; charset=koi8-r
567Content-Transfer-Encoding: 8bit
568MIME-Version: 1.0
569
570\372\304\322\301\327\323\324\327\325\312\324\305\41"))
571 (switch-to-buffer (get-buffer-create "*test*"))
572 (erase-buffer)
573 (set-buffer-multibyte nil)
574 (insert mail)
575 (rmail-mime-show t)
576 (set-buffer-multibyte t)))
577
e8652dd9 578
186f7f0b
KH
579(defun rmail-mime-insert-image (entity)
580 "Decode and insert the image body of MIME-entity ENTITY."
581 (let* ((content-type (car (rmail-mime-entity-type entity)))
582 (bulk-data (aref (rmail-mime-entity-tagline entity) 1))
583 (body (rmail-mime-entity-body entity))
584 data)
585 (if (stringp (aref body 0))
586 (setq data (aref body 0))
587 (let ((rmail-mime-mbox-buffer rmail-view-buffer)
588 (transfer-encoding (rmail-mime-entity-transfer-encoding entity)))
d1be4ec2
KH
589 (with-temp-buffer
590 (set-buffer-multibyte nil)
591 (setq buffer-undo-list t)
186f7f0b
KH
592 (insert-buffer-substring rmail-mime-mbox-buffer
593 (aref body 0) (aref body 1))
d1be4ec2
KH
594 (cond ((string= transfer-encoding "base64")
595 (ignore-errors (base64-decode-region (point-min) (point-max))))
596 ((string= transfer-encoding "quoted-printable")
597 (quoted-printable-decode-region (point-min) (point-max))))
598 (setq data
599 (buffer-substring-no-properties (point-min) (point-max))))))
186f7f0b
KH
600 (insert-image (create-image data (cdr bulk-data) t))
601 (insert "\n")))
e8652dd9 602
e7ca0062
KH
603(defun rmail-mime-toggle-button (button)
604 "Hide or show the body of the MIME-entity associated with BUTTON."
186f7f0b 605 (save-excursion
e7ca0062 606 (goto-char (button-start button))
186f7f0b 607 (rmail-mime-toggle-hidden)))
e8652dd9 608
e7ca0062 609(define-button-type 'rmail-mime-toggle 'action 'rmail-mime-toggle-button)
e8652dd9
GM
610
611
537ab246
BG
612(defun rmail-mime-bulk-handler (content-type
613 content-disposition
e8652dd9 614 content-transfer-encoding)
2e9075d3 615 "Handle the current buffer as an attachment to download.
e8652dd9
GM
616For images that Emacs is capable of displaying, the behavior
617depends upon the value of `rmail-mime-show-images'."
d1be4ec2
KH
618 (rmail-mime-insert-bulk
619 (rmail-mime-entity content-type content-disposition content-transfer-encoding
186f7f0b
KH
620 (vector (vector nil nil nil) (vector nil t nil))
621 (vector nil nil nil) (vector "" (cons nil nil) t)
622 (vector nil nil nil) nil 'rmail-mime-insert-bulk)))
623
624(defun rmail-mime-set-bulk-data (entity)
625 "Setup the information about the attachment object for MIME-entity ENTITY.
626The value is non-nil if and only if the attachment object should be shown
627directly."
628 (let ((content-type (car (rmail-mime-entity-type entity)))
629 (size (cdr (assq 'size (cdr (rmail-mime-entity-disposition entity)))))
630 (bulk-data (aref (rmail-mime-entity-tagline entity) 1))
631 (body (rmail-mime-entity-body entity))
e7ca0062 632 type to-show)
186f7f0b
KH
633 (cond (size
634 (setq size (string-to-number size)))
635 ((stringp (aref body 0))
636 (setq size (length (aref body 0))))
637 (t
638 ;; Rough estimation of the size.
639 (let ((encoding (rmail-mime-entity-transfer-encoding entity)))
640 (setq size (- (aref body 1) (aref body 0)))
641 (cond ((string= encoding "base64")
642 (setq size (/ (* size 3) 4)))
643 ((string= encoding "quoted-printable")
644 (setq size (/ (* size 7) 3)))))))
645
646 (cond
647 ((string-match "text/" content-type)
648 (setq type 'text))
649 ((string-match "image/\\(.*\\)" content-type)
650 (setq type (image-type-from-file-name
651 (concat "." (match-string 1 content-type))))
652 (if (and (memq type image-types)
653 (image-type-available-p type))
654 (if (and rmail-mime-show-images
655 (not (eq rmail-mime-show-images 'button))
656 (or (not (numberp rmail-mime-show-images))
657 (< size rmail-mime-show-images)))
658 (setq to-show t))
659 (setq type nil))))
660 (setcar bulk-data size)
661 (setcdr bulk-data type)
662 to-show))
d1be4ec2
KH
663
664(defun rmail-mime-insert-bulk (entity)
186f7f0b 665 "Presentation handler for an attachment MIME entity."
d1be4ec2
KH
666 (let* ((content-type (rmail-mime-entity-type entity))
667 (content-disposition (rmail-mime-entity-disposition entity))
186f7f0b
KH
668 (current (aref (rmail-mime-entity-display entity) 0))
669 (new (aref (rmail-mime-entity-display entity) 1))
670 (header (rmail-mime-entity-header entity))
671 (tagline (rmail-mime-entity-tagline entity))
672 (bulk-data (aref tagline 1))
d1be4ec2 673 (body (rmail-mime-entity-body entity))
e7ca0062 674 ;; Find the default directory for this media type.
d1be4ec2 675 (directory (catch 'directory
537ab246
BG
676 (dolist (entry rmail-mime-attachment-dirs-alist)
677 (when (string-match (car entry) (car content-type))
678 (dolist (dir (cdr entry))
679 (when (file-directory-p dir)
680 (throw 'directory dir)))))))
681 (filename (or (cdr (assq 'name (cdr content-type)))
682 (cdr (assq 'filename (cdr content-disposition)))
683 "noname"))
69220882 684 (units '(B kB MB GB))
186f7f0b
KH
685 (segment (rmail-mime-entity-segment (point) entity))
686 beg data size)
687
688 (if (integerp (aref body 0))
d1be4ec2 689 (setq data entity
186f7f0b
KH
690 size (car bulk-data))
691 (if (stringp (aref body 0))
692 (setq data (aref body 0))
693 (setq data (string-as-unibyte (buffer-string)))
694 (aset body 0 data)
695 (rmail-mime-set-bulk-data entity)
696 (delete-region (point-min) (point-max)))
697 (setq size (length data)))
d1be4ec2 698 (while (and (> size 1024.0) ; cribbed from gnus-agent-expire-done-message
69220882
GM
699 (cdr units))
700 (setq size (/ size 1024.0)
701 units (cdr units)))
186f7f0b
KH
702
703 (setq beg (point))
704
705 ;; header
706 (if (eq (aref current 0) (aref new 0))
707 (goto-char (aref segment 2))
708 (if (aref current 0)
709 (delete-char (- (aref segment 2) (aref segment 1))))
710 (if (aref new 0)
711 (rmail-mime-insert-header header)))
712
713 ;; tagline
714 (if (eq (aref current 1) (aref new 1))
e7ca0062
KH
715 (if (or (not (aref current 1))
716 (eq (aref current 2) (aref new 2)))
717 (forward-char (- (aref segment 3) (aref segment 2)))
718 (rmail-mime-update-tagline entity))
186f7f0b
KH
719 (if (aref current 1)
720 (delete-char (- (aref segment 3) (aref segment 2))))
721 (if (aref new 1)
722 (rmail-mime-insert-tagline
723 entity
e7ca0062 724 " Save:"
186f7f0b
KH
725 (list filename
726 :type 'rmail-mime-save
727 'help-echo "mouse-2, RET: Save attachment"
728 'filename filename
729 'directory (file-name-as-directory directory)
730 'data data)
731 (format " (%.0f%s)" size (car units))
e7ca0062
KH
732 ;; We don't need this button because the "type" string of a
733 ;; tagline is the button to do this.
734 ;; (if (cdr bulk-data)
735 ;; " ")
736 ;; (if (cdr bulk-data)
737 ;; (list "Toggle show/hide"
738 ;; :type 'rmail-mime-image
739 ;; 'help-echo "mouse-2, RET: Toggle show/hide"
740 ;; 'image-type (cdr bulk-data)
741 ;; 'image-data data))
742 )))
186f7f0b
KH
743 ;; body
744 (if (eq (aref current 2) (aref new 2))
745 (forward-char (- (aref segment 4) (aref segment 3)))
746 (if (aref current 2)
747 (delete-char (- (aref segment 4) (aref segment 3))))
748 (if (aref new 2)
749 (cond ((eq (cdr bulk-data) 'text)
750 (rmail-mime-insert-decoded-text entity))
751 ((cdr bulk-data)
362b9d48
GM
752 (rmail-mime-insert-image entity))
753 (t
754 ;; As we don't know how to display the body, just
755 ;; insert it as a text.
756 (rmail-mime-insert-decoded-text entity)))))
186f7f0b 757 (put-text-property beg (point) 'rmail-mime-entity entity)))
537ab246
BG
758
759(defun test-rmail-mime-bulk-handler ()
760 "Test of a mail used as an example in RFC 2183."
761 (let ((mail "Content-Type: image/jpeg
762Content-Disposition: attachment; filename=genome.jpeg;
763 modification-date=\"Wed, 12 Feb 1997 16:29:51 -0500\";
764Content-Description: a complete map of the human genome
765Content-Transfer-Encoding: base64
766
767iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAABGdBTUEAALGPC/xhBQAAAAZQ
768TFRF////AAAAVcLTfgAAAPZJREFUeNq9ldsOwzAIQ+3//+l1WlvA5ZLsoUiTto4TB+ISoAjy
769+ITfRBfcAmgRFFeAm+J6uhdKdFhFWUgDkFsK0oUp/9G2//Kj7Jx+5tSKOdBscgUYiKHRS/me
770WATQdRUvAK0Bnmshmtn79PpaLBbbOZkjKvRnjRZoRswOkG1wFchKew2g9wXVJVZL/m4+B+vv
7719AxQQR2Q33SgAYJzzVACdAWjAfRYzYFO9n6SLnydtQHSMxYDMAKqZ/8FS/lTK+zuq3CtK64L
772UDwbgUEAUmk2Zyg101d6PhCDySgAvTvDgKiuOrc4dLxUb7UMnhGIexyI+d6U+ABuNAP4Simx
773lgAAAABJRU5ErkJggg==
774"))
775 (switch-to-buffer (get-buffer-create "*test*"))
776 (erase-buffer)
777 (insert mail)
778 (rmail-mime-show)))
779
780(defun rmail-mime-multipart-handler (content-type
781 content-disposition
782 content-transfer-encoding)
783 "Handle the current buffer as a multipart MIME body.
784The current buffer should be narrowed to the body. CONTENT-TYPE,
785CONTENT-DISPOSITION, and CONTENT-TRANSFER-ENCODING are the values
786of the respective parsed headers. See `rmail-mime-handle' for their
787format."
d1be4ec2 788 (rmail-mime-process-multipart
186f7f0b
KH
789 content-type content-disposition content-transfer-encoding nil)
790 t)
d1be4ec2
KH
791
792(defun rmail-mime-process-multipart (content-type
793 content-disposition
794 content-transfer-encoding
186f7f0b 795 parse-tag)
d1be4ec2
KH
796 "Process the current buffer as a multipart MIME body.
797
186f7f0b
KH
798If PARSE-TAG is nil, modify the current buffer directly for
799showing the MIME body and return nil.
d1be4ec2 800
186f7f0b
KH
801Otherwise, PARSE-TAG is a string indicating the depth and index
802number of the entity. In this case, parse the current buffer and
803return a list of MIME-entity objects.
d1be4ec2
KH
804
805The other arguments are the same as `rmail-mime-multipart-handler'."
537ab246
BG
806 ;; Some MUAs start boundaries with "--", while it should start
807 ;; with "CRLF--", as defined by RFC 2046:
808 ;; The boundary delimiter MUST occur at the beginning of a line,
809 ;; i.e., following a CRLF, and the initial CRLF is considered to
810 ;; be attached to the boundary delimiter line rather than part
811 ;; of the preceding part.
812 ;; We currently don't handle that.
813 (let ((boundary (cdr (assq 'boundary content-type)))
186f7f0b
KH
814 (subtype (cadr (split-string (car content-type) "/")))
815 (index 0)
d1be4ec2 816 beg end next entities)
537ab246
BG
817 (unless boundary
818 (rmail-mm-get-boundary-error-message
819 "No boundary defined" content-type content-disposition
820 content-transfer-encoding))
821 (setq boundary (concat "\n--" boundary))
822 ;; Hide the body before the first bodypart
823 (goto-char (point-min))
824 (when (and (search-forward boundary nil t)
825 (looking-at "[ \t]*\n"))
186f7f0b 826 (if parse-tag
d1be4ec2
KH
827 (narrow-to-region (match-end 0) (point-max))
828 (delete-region (point-min) (match-end 0))))
186f7f0b
KH
829
830 ;; Change content-type to the proper default one for the children.
831 (cond ((string-match "mixed" subtype)
832 (setq content-type '("text/plain")))
833 ((string-match "digest" subtype)
362b9d48
GM
834 (setq content-type '("message/rfc822")))
835 (t
836 (setq content-type nil)))
186f7f0b 837
537ab246
BG
838 ;; Loop over all body parts, where beg points at the beginning of
839 ;; the part and end points at the end of the part. next points at
186f7f0b
KH
840 ;; the beginning of the next part. The current point is just
841 ;; after the boundary tag.
537ab246
BG
842 (setq beg (point-min))
843 (while (search-forward boundary nil t)
844 (setq end (match-beginning 0))
845 ;; If this is the last boundary according to RFC 2046, hide the
846 ;; epilogue, else hide the boundary only. Use a marker for
847 ;; `next' because `rmail-mime-show' may change the buffer.
ffa1fed6 848 (cond ((looking-at "--[ \t]*$")
537ab246
BG
849 (setq next (point-max-marker)))
850 ((looking-at "[ \t]*\n")
ffa1fed6 851 (setq next (copy-marker (match-end 0) t)))
537ab246
BG
852 (t
853 (rmail-mm-get-boundary-error-message
854 "Malformed boundary" content-type content-disposition
855 content-transfer-encoding)))
186f7f0b
KH
856
857 (setq index (1+ index))
537ab246 858 ;; Handle the part.
186f7f0b 859 (if parse-tag
d1be4ec2
KH
860 (save-restriction
861 (narrow-to-region beg end)
186f7f0b
KH
862 (let ((child (rmail-mime-process
863 nil (format "%s/%d" parse-tag index)
864 content-type content-disposition)))
865 ;; Display a tagline.
866 (aset (aref (rmail-mime-entity-display child) 1) 1
867 (aset (rmail-mime-entity-tagline child) 2 t))
868 (push child entities)))
869
d1be4ec2
KH
870 (delete-region end next)
871 (save-restriction
872 (narrow-to-region beg end)
873 (rmail-mime-show)))
874 (goto-char (setq beg next)))
186f7f0b
KH
875
876 (when parse-tag
877 (setq entities (nreverse entities))
878 (if (string-match "alternative" subtype)
879 ;; Find the best entity to show, and hide all the others.
880 (let (best second)
881 (dolist (child entities)
882 (if (string= (or (car (rmail-mime-entity-disposition child))
883 (car content-disposition))
884 "inline")
885 (if (string-match "text/plain"
886 (car (rmail-mime-entity-type child)))
887 (setq best child)
888 (if (string-match "text/.*"
889 (car (rmail-mime-entity-type child)))
890 (setq second child)))))
891 (or best (not second) (setq best second))
892 (dolist (child entities)
e7ca0062
KH
893 (unless (eq best child)
894 (aset (rmail-mime-entity-body child) 2 nil)
895 (rmail-mime-hidden-mode child)))))
186f7f0b 896 entities)))
537ab246
BG
897
898(defun test-rmail-mime-multipart-handler ()
899 "Test of a mail used as an example in RFC 2046."
900 (let ((mail "From: Nathaniel Borenstein <nsb@bellcore.com>
901To: Ned Freed <ned@innosoft.com>
902Date: Sun, 21 Mar 1993 23:56:48 -0800 (PST)
903Subject: Sample message
904MIME-Version: 1.0
905Content-type: multipart/mixed; boundary=\"simple boundary\"
906
907This is the preamble. It is to be ignored, though it
908is a handy place for composition agents to include an
909explanatory note to non-MIME conformant readers.
910
911--simple boundary
912
913This is implicitly typed plain US-ASCII text.
914It does NOT end with a linebreak.
915--simple boundary
916Content-type: text/plain; charset=us-ascii
917
918This is explicitly typed plain US-ASCII text.
919It DOES end with a linebreak.
920
921--simple boundary--
922
923This is the epilogue. It is also to be ignored."))
924 (switch-to-buffer (get-buffer-create "*test*"))
925 (erase-buffer)
926 (insert mail)
927 (rmail-mime-show t)))
928
186f7f0b
KH
929(defun rmail-mime-insert-multipart (entity)
930 "Presentation handler for a multipart MIME entity."
931 (let ((current (aref (rmail-mime-entity-display entity) 0))
932 (new (aref (rmail-mime-entity-display entity) 1))
933 (header (rmail-mime-entity-header entity))
934 (tagline (rmail-mime-entity-tagline entity))
935 (body (rmail-mime-entity-body entity))
936 (beg (point))
937 (segment (rmail-mime-entity-segment (point) entity)))
938 ;; header
939 (if (eq (aref current 0) (aref new 0))
940 (goto-char (aref segment 2))
941 (if (aref current 0)
942 (delete-char (- (aref segment 2) (aref segment 1))))
943 (if (aref new 0)
944 (rmail-mime-insert-header header)))
945 ;; tagline
946 (if (eq (aref current 1) (aref new 1))
e7ca0062
KH
947 (if (or (not (aref current 1))
948 (eq (aref current 2) (aref new 2)))
949 (forward-char (- (aref segment 3) (aref segment 2)))
950 (rmail-mime-update-tagline entity))
186f7f0b
KH
951 (if (aref current 1)
952 (delete-char (- (aref segment 3) (aref segment 2))))
953 (if (aref new 1)
954 (rmail-mime-insert-tagline entity)))
955
956 (put-text-property beg (point) 'rmail-mime-entity entity)
e7ca0062 957
186f7f0b
KH
958 ;; body
959 (if (eq (aref current 2) (aref new 2))
960 (forward-char (- (aref segment 4) (aref segment 3)))
e7ca0062
KH
961 (dolist (child (rmail-mime-entity-children entity))
962 (rmail-mime-insert child)))
963 entity))
186f7f0b 964
537ab246
BG
965;;; Main code
966
967(defun rmail-mime-handle (content-type
968 content-disposition
969 content-transfer-encoding)
970 "Handle the current buffer as a MIME part.
971The current buffer should be narrowed to the respective body, and
972point should be at the beginning of the body.
973
974CONTENT-TYPE, CONTENT-DISPOSITION, and CONTENT-TRANSFER-ENCODING
0c9ff2c5
GM
975are the values of the respective parsed headers. The latter should
976be downcased. The parsed headers for CONTENT-TYPE and CONTENT-DISPOSITION
977have the form
537ab246
BG
978
979 \(VALUE . ALIST)
980
981In other words:
982
983 \(VALUE (ATTRIBUTE . VALUE) (ATTRIBUTE . VALUE) ...)
984
985VALUE is a string and ATTRIBUTE is a symbol.
986
987Consider the following header, for example:
988
989Content-Type: multipart/mixed;
990 boundary=\"----=_NextPart_000_0104_01C617E4.BDEC4C40\"
991
992The parsed header value:
993
994\(\"multipart/mixed\"
995 \(\"boundary\" . \"----=_NextPart_000_0104_01C617E4.BDEC4C40\"))"
996 ;; Handle the content transfer encodings we know. Unknown transfer
997 ;; encodings will be passed on to the various handlers.
998 (cond ((string= content-transfer-encoding "base64")
999 (when (ignore-errors
1000 (base64-decode-region (point) (point-max)))
1001 (setq content-transfer-encoding nil)))
1002 ((string= content-transfer-encoding "quoted-printable")
1003 (quoted-printable-decode-region (point) (point-max))
1004 (setq content-transfer-encoding nil))
1005 ((string= content-transfer-encoding "8bit")
1006 ;; FIXME: Is this the correct way?
c893016b
SM
1007 ;; No, of course not, it just means there's no decoding to do.
1008 ;; (set-buffer-multibyte nil)
1009 (setq content-transfer-encoding nil)
1010 ))
537ab246
BG
1011 ;; Inline stuff requires work. Attachments are handled by the bulk
1012 ;; handler.
1013 (if (string= "inline" (car content-disposition))
1014 (let ((stop nil))
1015 (dolist (entry rmail-mime-media-type-handlers-alist)
1016 (when (and (string-match (car entry) (car content-type)) (not stop))
1017 (progn
1018 (setq stop (funcall (cadr entry) content-type
1019 content-disposition
1020 content-transfer-encoding))))))
1021 ;; Everything else is an attachment.
1022 (rmail-mime-bulk-handler content-type
1023 content-disposition
e7ca0062
KH
1024 content-transfer-encoding))
1025 (save-restriction
1026 (widen)
1027 (let ((entity (get-text-property (1- (point)) 'rmail-mime-entity))
1028 current new)
1029 (when entity
1030 (setq current (aref (rmail-mime-entity-display entity) 0)
1031 new (aref (rmail-mime-entity-display entity) 1))
1032 (dotimes (i 3)
1033 (aset current i (aref new i)))))))
537ab246
BG
1034
1035(defun rmail-mime-show (&optional show-headers)
1036 "Handle the current buffer as a MIME message.
1037If SHOW-HEADERS is non-nil, then the headers of the current part
1038will shown as usual for a MIME message. The headers are also
1039shown for the content type message/rfc822. This function will be
1040called recursively if multiple parts are available.
1041
1042The current buffer must contain a single message. It will be
1043modified."
d1be4ec2
KH
1044 (rmail-mime-process show-headers nil))
1045
186f7f0b
KH
1046(defun rmail-mime-process (show-headers parse-tag &optional
1047 default-content-type
1048 default-content-disposition)
537ab246
BG
1049 (let ((end (point-min))
1050 content-type
1051 content-transfer-encoding
1052 content-disposition)
1053 ;; `point-min' returns the beginning and `end' points at the end
1054 ;; of the headers.
1055 (goto-char (point-min))
1056 ;; If we're showing a part without headers, then it will start
1057 ;; with a newline.
1058 (if (eq (char-after) ?\n)
1059 (setq end (1+ (point)))
1060 (when (search-forward "\n\n" nil t)
1061 (setq end (match-end 0))
1062 (save-restriction
1063 (narrow-to-region (point-min) end)
1064 ;; FIXME: Default disposition of the multipart entities should
1065 ;; be inherited.
1066 (setq content-type
1067 (mail-fetch-field "Content-Type")
1068 content-transfer-encoding
1069 (mail-fetch-field "Content-Transfer-Encoding")
1070 content-disposition
1071 (mail-fetch-field "Content-Disposition")))))
0c9ff2c5
GM
1072 ;; Per RFC 2045, C-T-E is case insensitive (bug#5070), but the others
1073 ;; are not completely so. Hopefully mail-header-parse-* DTRT.
1074 (if content-transfer-encoding
1075 (setq content-transfer-encoding (downcase content-transfer-encoding)))
1076 (setq content-type
1077 (if content-type
e7ca0062
KH
1078 (or (mail-header-parse-content-type content-type)
1079 '("text/plain"))
186f7f0b 1080 (or default-content-type '("text/plain"))))
537ab246
BG
1081 (setq content-disposition
1082 (if content-disposition
1083 (mail-header-parse-content-disposition content-disposition)
1084 ;; If none specified, we are free to choose what we deem
1085 ;; suitable according to RFC 2183. We like inline.
186f7f0b 1086 (or default-content-disposition '("inline"))))
537ab246
BG
1087 ;; Unrecognized disposition types are to be treated like
1088 ;; attachment according to RFC 2183.
1089 (unless (member (car content-disposition) '("inline" "attachment"))
1090 (setq content-disposition '("attachment")))
d1be4ec2 1091
186f7f0b
KH
1092 (if parse-tag
1093 (let* ((is-inline (string= (car content-disposition) "inline"))
1094 (header (vector (point-min) end nil))
1095 (tagline (vector parse-tag (cons nil nil) t))
1096 (body (vector end (point-max) is-inline))
1097 (new (vector (aref header 2) (aref tagline 2) (aref body 2)))
1098 children handler entity)
1099 (cond ((string-match "multipart/.*" (car content-type))
1100 (save-restriction
1101 (narrow-to-region (1- end) (point-max))
1102 (setq children (rmail-mime-process-multipart
1103 content-type
1104 content-disposition
1105 content-transfer-encoding
1106 parse-tag)
1107 handler 'rmail-mime-insert-multipart)))
1108 ((string-match "message/rfc822" (car content-type))
1109 (save-restriction
d1be4ec2 1110 (narrow-to-region end (point-max))
186f7f0b
KH
1111 (let* ((msg (rmail-mime-process t parse-tag
1112 '("text/plain") '("inline")))
1113 (msg-new (aref (rmail-mime-entity-display msg) 1)))
1114 ;; Show header of the child.
1115 (aset msg-new 0 t)
1116 (aset (rmail-mime-entity-header msg) 2 t)
1117 ;; Hide tagline of the child.
1118 (aset msg-new 1 nil)
1119 (aset (rmail-mime-entity-tagline msg) 2 nil)
1120 (setq children (list msg)
1121 handler 'rmail-mime-insert-multipart))))
1122 ((and is-inline (string-match "text/" (car content-type)))
1123 ;; Don't need a tagline.
1124 (aset new 1 (aset tagline 2 nil))
1125 (setq handler 'rmail-mime-insert-text))
1126 (t
1127 ;; Force hidden mode.
1128 (aset new 1 (aset tagline 2 t))
1129 (aset new 2 (aset body 2 nil))
1130 (setq handler 'rmail-mime-insert-bulk)))
1131 (setq entity (rmail-mime-entity content-type
1132 content-disposition
1133 content-transfer-encoding
1134 (vector (vector nil nil nil) new)
1135 header tagline body children handler))
1136 (if (and (eq handler 'rmail-mime-insert-bulk)
1137 (rmail-mime-set-bulk-data entity))
1138 ;; Show the body.
1139 (aset new 2 (aset body 2 t)))
1140 entity)
1141
d1be4ec2 1142 ;; Hide headers and handle the part.
186f7f0b 1143 (put-text-property (point-min) (point-max) 'rmail-mime-entity
7c420169 1144 (rmail-mime-entity
186f7f0b
KH
1145 content-type content-disposition
1146 content-transfer-encoding
1147 (vector (vector 'raw nil 'raw) (vector 'raw nil 'raw))
1148 (vector nil nil 'raw) (vector "" (cons nil nil) nil)
1149 (vector nil nil 'raw) nil nil))
d1be4ec2
KH
1150 (save-restriction
1151 (cond ((string= (car content-type) "message/rfc822")
1152 (narrow-to-region end (point-max)))
1153 ((not show-headers)
1154 (delete-region (point-min) end)))
1155 (rmail-mime-handle content-type content-disposition
1156 content-transfer-encoding)))))
1157
d1be4ec2
KH
1158(defun rmail-mime-parse ()
1159 "Parse the current Rmail message as a MIME message.
8258ae3f
KH
1160The value is a MIME-entiy object (see `rmail-mime-entity').
1161If an error occurs, return an error message string."
186f7f0b
KH
1162 (let ((rmail-mime-mbox-buffer (if (rmail-buffers-swapped-p)
1163 rmail-view-buffer
1164 (current-buffer))))
8258ae3f 1165 (condition-case err
186f7f0b
KH
1166 (with-current-buffer rmail-mime-mbox-buffer
1167 (save-excursion
1168 (goto-char (point-min))
1169 (let* ((entity (rmail-mime-process t ""
1170 '("text/plain") '("inline")))
1171 (new (aref (rmail-mime-entity-display entity) 1)))
1172 ;; Show header.
1173 (aset new 0 (aset (rmail-mime-entity-header entity) 2 t))
1174 ;; Show tagline if and only if body is not shown.
1175 (if (aref new 2)
1176 (aset new 1 (aset (rmail-mime-entity-tagline entity) 2 nil))
1177 (aset new 1 (aset (rmail-mime-entity-tagline entity) 2 t)))
1178 entity)))
8258ae3f 1179 (error (format "%s" err)))))
186f7f0b
KH
1180
1181(defun rmail-mime-insert (entity)
d1be4ec2
KH
1182 "Insert a MIME-entity ENTITY in the current buffer.
1183
1184This function will be called recursively if multiple parts are
1185available."
186f7f0b
KH
1186 (let ((current (aref (rmail-mime-entity-display entity) 0))
1187 (new (aref (rmail-mime-entity-display entity) 1)))
1188 (if (not (eq (aref new 0) 'raw))
1189 ;; Not a raw-mode. Each handler should handle it.
1190 (funcall (rmail-mime-entity-handler entity) entity)
1191 (let ((header (rmail-mime-entity-header entity))
1192 (tagline (rmail-mime-entity-tagline entity))
1193 (body (rmail-mime-entity-body entity))
1194 (beg (point))
1195 (segment (rmail-mime-entity-segment (point) entity)))
1196 ;; header
1197 (if (eq (aref current 0) (aref new 0))
1198 (goto-char (aref segment 2))
1199 (if (aref current 0)
1200 (delete-char (- (aref segment 2) (aref segment 1))))
1201 (insert-buffer-substring rmail-mime-mbox-buffer
1202 (aref header 0) (aref header 1)))
1203 ;; tagline
1204 (if (aref current 1)
1205 (delete-char (- (aref segment 3) (aref segment 2))))
1206 ;; body
e7ca0062
KH
1207 (let ((children (rmail-mime-entity-children entity)))
1208 (if children
1209 (progn
1210 (put-text-property beg (point) 'rmail-mime-entity entity)
1211 (dolist (child children)
1212 (rmail-mime-insert child)))
1213 (if (eq (aref current 2) (aref new 2))
1214 (forward-char (- (aref segment 4) (aref segment 3)))
1215 (if (aref current 2)
1216 (delete-char (- (aref segment 4) (aref segment 3))))
1217 (insert-buffer-substring rmail-mime-mbox-buffer
1218 (aref body 0) (aref body 1))
1219 (or (bolp) (insert "\n")))
1220 (put-text-property beg (point) 'rmail-mime-entity entity)))))
186f7f0b
KH
1221 (dotimes (i 3)
1222 (aset current i (aref new i)))))
537ab246 1223
2e9075d3
GM
1224(define-derived-mode rmail-mime-mode fundamental-mode "RMIME"
1225 "Major mode used in `rmail-mime' buffers."
1226 (setq font-lock-defaults '(rmail-font-lock-keywords t t nil nil)))
1227
73422054 1228;;;###autoload
186f7f0b
KH
1229(defun rmail-mime (&optional arg)
1230 "Toggle displaying of a MIME message.
1231
1232The actualy behavior depends on the value of `rmail-enable-mime'.
1233
1234If `rmail-enable-mime' is t (default), this command change the
1235displaying of a MIME message between decoded presentation form
1236and raw data.
1237
1238With ARG, toggle the displaying of the current MIME entity only.
1239
1240If `rmail-enable-mime' is nil, this creates a temporary
1241\"*RMAIL*\" buffer holding a decoded copy of the message. Inline
1242content-types are handled according to
f4ce6150
GM
1243`rmail-mime-media-type-handlers-alist'. By default, this
1244displays text and multipart messages, and offers to download
1245attachments as specfied by `rmail-mime-attachment-dirs-alist'."
186f7f0b
KH
1246 (interactive "P")
1247 (if rmail-enable-mime
e7ca0062
KH
1248 (with-current-buffer rmail-buffer
1249 (if (rmail-mime-message-p)
1250 (let ((rmail-mime-mbox-buffer rmail-view-buffer)
1251 (rmail-mime-view-buffer rmail-buffer)
1252 (entity (get-text-property (point) 'rmail-mime-entity)))
1253 (if arg
1254 (if entity
1255 (rmail-mime-toggle-raw entity))
1256 (goto-char (point-min))
1257 (rmail-mime-toggle-raw
1258 (get-text-property (point) 'rmail-mime-entity))))
1259 (message "Not a MIME message")))
186f7f0b
KH
1260 (let* ((data (rmail-apply-in-message rmail-current-message 'buffer-string))
1261 (buf (get-buffer-create "*RMAIL*"))
1262 (rmail-mime-mbox-buffer rmail-view-buffer)
1263 (rmail-mime-view-buffer buf))
1264 (set-buffer buf)
1265 (setq buffer-undo-list t)
1266 (let ((inhibit-read-only t))
1267 ;; Decoding the message in fundamental mode for speed, only
1268 ;; switching to rmail-mime-mode at the end for display. Eg
1269 ;; quoted-printable-decode-region gets very slow otherwise (Bug#4993).
1270 (fundamental-mode)
1271 (erase-buffer)
1272 (insert data)
1273 (rmail-mime-show t)
1274 (rmail-mime-mode)
1275 (set-buffer-modified-p nil))
1276 (view-buffer buf))))
537ab246
BG
1277
1278(defun rmail-mm-get-boundary-error-message (message type disposition encoding)
1279 "Return MESSAGE with more information on the main mime components."
1280 (error "%s; type: %s; disposition: %s; encoding: %s"
1281 message type disposition encoding))
1282
d1be4ec2 1283(defun rmail-show-mime ()
7e116860 1284 "Function to set in `rmail-show-mime-function' (which see)."
186f7f0b
KH
1285 (let ((entity (rmail-mime-parse))
1286 (rmail-mime-mbox-buffer rmail-buffer)
1287 (rmail-mime-view-buffer rmail-view-buffer)
1288 (rmail-mime-coding-system nil))
8258ae3f 1289 (if (vectorp entity)
186f7f0b
KH
1290 (with-current-buffer rmail-mime-view-buffer
1291 (erase-buffer)
1292 (rmail-mime-insert entity)
1a6a03e4
KH
1293 (if (consp rmail-mime-coding-system)
1294 ;; Decoding is done by rfc2047-decode-region only for a
1295 ;; header. But, as the used coding system may have been
1296 ;; overriden by mm-charset-override-alist, we can't
1297 ;; trust (car rmail-mime-coding-system). So, here we
1298 ;; try the decoding again with mm-charset-override-alist
1299 ;; bound to nil.
1300 (let ((mm-charset-override-alist nil))
1301 (setq rmail-mime-coding-system
1302 (rmail-mime-find-header-encoding
1303 (rmail-mime-entity-header entity)))))
1304 (set-buffer-file-coding-system
1305 (coding-system-base rmail-mime-coding-system) t t))
8258ae3f
KH
1306 ;; Decoding failed. ENTITY is an error message. Insert the
1307 ;; original message body as is, and show warning.
186f7f0b 1308 (let ((region (with-current-buffer rmail-mime-mbox-buffer
7e116860
KH
1309 (goto-char (point-min))
1310 (re-search-forward "^$" nil t)
1311 (forward-line 1)
8258ae3f 1312 (vector (point-min) (point) (point-max)))))
186f7f0b 1313 (with-current-buffer rmail-mime-view-buffer
7e116860
KH
1314 (let ((inhibit-read-only t))
1315 (erase-buffer)
8258ae3f 1316 (rmail-mime-insert-header region)
186f7f0b 1317 (insert-buffer-substring rmail-mime-mbox-buffer
8258ae3f 1318 (aref region 1) (aref region 2))))
186f7f0b 1319 (set-buffer-file-coding-system 'no-conversion t t)
8258ae3f 1320 (message "MIME decoding failed: %s" entity)))))
d1be4ec2
KH
1321
1322(setq rmail-show-mime-function 'rmail-show-mime)
1323
1324(defun rmail-insert-mime-forwarded-message (forward-buffer)
7e116860 1325 "Function to set in `rmail-insert-mime-forwarded-message-function' (which see)."
186f7f0b
KH
1326 (let ((rmail-mime-mbox-buffer
1327 (with-current-buffer forward-buffer rmail-view-buffer)))
d1be4ec2
KH
1328 (save-restriction
1329 (narrow-to-region (point) (point))
186f7f0b 1330 (message-forward-make-body-mime rmail-mime-mbox-buffer))))
d1be4ec2
KH
1331
1332(setq rmail-insert-mime-forwarded-message-function
1333 'rmail-insert-mime-forwarded-message)
1334
1335(defun rmail-insert-mime-resent-message (forward-buffer)
7e116860 1336 "Function to set in `rmail-insert-mime-resent-message-function' (which see)."
d1be4ec2
KH
1337 (insert-buffer-substring
1338 (with-current-buffer forward-buffer rmail-view-buffer))
1339 (goto-char (point-min))
1340 (when (looking-at "From ")
1341 (forward-line 1)
1342 (delete-region (point-min) (point))))
1343
1344(setq rmail-insert-mime-resent-message-function
1345 'rmail-insert-mime-resent-message)
1346
7e116860
KH
1347(defun rmail-search-mime-message (msg regexp)
1348 "Function to set in `rmail-search-mime-message-function' (which see)."
1349 (save-restriction
1350 (narrow-to-region (rmail-msgbeg msg) (rmail-msgend msg))
186f7f0b
KH
1351 (let* ((rmail-mime-mbox-buffer (current-buffer))
1352 (rmail-mime-view-buffer rmail-view-buffer)
1353 (header-end (save-excursion
1354 (re-search-forward "^$" nil 'move) (point)))
1355 (body-end (point-max))
1356 (entity (rmail-mime-parse)))
7c420169 1357 (or
7e116860
KH
1358 ;; At first, just search the headers.
1359 (with-temp-buffer
186f7f0b 1360 (insert-buffer-substring rmail-mime-mbox-buffer nil header-end)
7e116860
KH
1361 (rfc2047-decode-region (point-min) (point))
1362 (goto-char (point-min))
1363 (re-search-forward regexp nil t))
1364 ;; Next, search the body.
1365 (if (and entity
1366 (let* ((content-type (rmail-mime-entity-type entity))
1367 (charset (cdr (assq 'charset (cdr content-type)))))
7c420169 1368 (or (not (string-match "text/.*" (car content-type)))
7e116860
KH
1369 (and charset
1370 (not (string= (downcase charset) "us-ascii"))))))
1371 ;; Search the decoded MIME message.
1372 (with-temp-buffer
186f7f0b 1373 (rmail-mime-insert entity)
7e116860
KH
1374 (goto-char (point-min))
1375 (re-search-forward regexp nil t))
1376 ;; Search the body without decoding.
1377 (goto-char header-end)
1378 (re-search-forward regexp nil t))))))
1379
1380(setq rmail-search-mime-message-function 'rmail-search-mime-message)
1381
537ab246
BG
1382(provide 'rmailmm)
1383
35426db4
GM
1384;; Local Variables:
1385;; generated-autoload-file: "rmail.el"
1386;; End:
1387
537ab246 1388;;; rmailmm.el ends here