2 ;;; A MIME viewer for GNU Emacs
4 ;;; by Morioka Tomohiko, 1994/07/13
12 (defconst mime/viewer-RCS-ID
13 "$Id: tm-view.el,v 3.8 1994/09/02 10:32:31 morioka Exp $")
15 (defconst mime/viewer-version
16 (and (string-match "[0-9][0-9.]*" mime/viewer-RCS-ID)
17 (substring mime/viewer-RCS-ID (match-beginning 0)(match-end 0))
34 (defconst mime/tspecials "\000-\040()<>@,;:\\\"/[\093?.=")
35 (defconst mime/token-regexp
36 (concat "[^" mime/tspecials "]*"))
37 (defconst mime/content-type-subtype-regexp
38 (concat mime/token-regexp "/" mime/token-regexp))
39 (defconst mime/content-parameter-value-regexp
41 message/quoted-string-regexp
44 (defconst mime/output-buffer-name "*MIME-out*")
45 (defconst mime/decoding-buffer-name "*MIME-decoding*")
51 (defvar mime/content-decoding-method-alist
52 '(("text/plain" . "tm-plain")
53 ("text/x-latex" . "tm-latex")
54 ("audio/basic" . "tm-au")
55 ("image/gif" . "tm-image")
56 ("image/jpeg" . "tm-image")
57 ("image/tiff" . "tm-image")
58 ("image/x-tiff" . "tm-image")
59 ("image/x-xbm" . "tm-image")
60 ("image/x-pic" . "tm-image")
61 ("video/mpeg" . "tm-mpeg")
62 ("application/octet-stream" . "tm-file")
65 (defvar mime/default-showing-Content-Type-list
66 '("text/plain" "text/x-latex" "message/rfc822"))
68 (defvar mime/go-to-top-node-method-alist
69 '((gnus-article-mode . (lambda ()
70 (gnus-article-show-summary)
72 (rmail-mode . (lambda ()
75 (delete-other-windows)
77 (mh-show-mode . (lambda ()
79 (let ((name (buffer-name)))
80 (string-match "show-" name)
81 (substring name (match-end 0))
84 (mime/show-message-mode . (lambda ()
85 (set-window-configuration
86 mime/show-mode-old-window-configuration)
87 (let ((buf (current-buffer)))
88 (pop-to-buffer mime/mother-buffer)
93 (defvar mime/tmp-dir "/tmp/")
95 (defvar mime/hide-content-header nil)
97 (defvar mime/use-internal-decoder nil)
99 (defvar mime/body-decoding-mode "play" "MIME body decoding mode")
105 (defun mime/parse-content ()
108 (mime/decode-message-header)
109 (goto-char (point-min))
110 (let* ((ctl (mime/Content-Type))
111 (boundary (assoc "boundary" (cdr ctl)))
113 (search-forward "\n\n" nil t)
115 (let ((sep (concat "\n--"
117 (message/strip-quoted-string
121 (setq beg (match-end 0))
122 (search-forward (concat "\n--" boundary "--\n") nil t)
123 (setq end (match-beginning 0))
126 (narrow-to-region beg end)
127 (goto-char (point-min))
128 (search-forward (concat "--" boundary "\n") nil t)
129 (setq cb (match-end 0))
130 (while (search-forward sep nil t)
131 (setq ce (match-beginning 0))
132 (setq ncb (match-end 0))
135 (narrow-to-region cb ce)
136 (setq ret (mime/parse-content))
138 (setq dest (append dest (list ret)))
139 (goto-char (nth 1 ret))
140 (search-forward (concat "--" boundary "\n") nil t)
141 (goto-char (setq cb (match-end 0)))
143 (setq ce (point-max))
146 (narrow-to-region cb ce)
147 (setq ret (mime/parse-content))
149 (setq dest (append dest (list ret)))
151 (setq beg (point-min))
153 (search-forward (concat "\n--" boundary "--\n") nil t)
154 (setq end (match-beginning 0))
156 ((string= (car ctl) "message/rfc822")
159 (narrow-to-region (match-end 0) (point-max))
160 (setq dest (list (mime/parse-content)))
162 (setq beg (point-min))
163 (setq end (point-max))
166 (setq beg (point-min))
167 (setq end (point-max))
172 (defun mime/Content-Type ()
175 (if (and (re-search-forward "^Content-Type:[ \t]*" nil t)
179 (and (re-search-forward ".*\\(\n[ \t].*\\)*" nil t)
182 (goto-char (point-min))
183 (re-search-forward mime/content-type-subtype-regexp nil t)
187 (buffer-substring (match-beginning 0) (match-end 0))
189 dest attribute value)
190 (while (and (re-search-forward "[ \t\n]*;[ \t\n]*" nil t)
191 (re-search-forward mime/token-regexp nil t)
195 (buffer-substring (match-beginning 0) (match-end 0))
197 (if (and (re-search-forward "=[ \t\n]*" nil t)
198 (re-search-forward mime/content-parameter-value-regexp
203 (buffer-substring (match-beginning 0)
211 (defun mime/Content-Transfer-Encoding (&optional default-encoding)
214 (if (and (re-search-forward "^Content-Transfer-Encoding:[ \t]*" nil t)
215 (re-search-forward mime/token-regexp nil t)
217 (downcase (buffer-substring (match-beginning 0) (match-end 0)))
221 (defun mime/get-name (ctype)
226 (or (and (setq ret (assoc "name" ctype))
227 (message/strip-quoted-string (cdr ret))
229 (and (setq ret (assoc "x-name" ctype))
230 (message/strip-quoted-string (cdr ret))
232 (message/get-field-body "Content-Description")
236 (defun mime/parse-message ()
240 (setq selective-display t)
241 (make-variable-buffer-local 'mime/content-list)
242 (let ((buffer-read-only nil))
243 (setq mime/content-list (mime/parse-content))
246 (set-buffer-modified-p nil)
250 ;;; @ content information
253 (defun mime/get-point-content-number (p &optional cl)
255 (setq cl mime/content-list)
261 (if (and (<= b p)(<= p e))
262 (or (let (co ret (sn 0))
266 (setq ret (mime/get-point-content-number p co))
267 (cond ((eq ret t) (throw 'tag (list sn)))
268 (ret (throw 'tag (cons sn ret)))
275 (defun mime/get-content-region (cn &optional cl)
277 (setq cl mime/content-list)
284 (let ((rcl (nth sn (nth 2 cl))))
286 (mime/get-content-region (cdr cn) rcl)
290 (defun mime/make-flat-content-list (&optional cl)
292 (setq cl mime/content-list)
294 (let ((dest (list cl))
298 (setq dest (append dest (mime/make-flat-content-list (car rcl))))
307 (defun mime/base64-decode-region (beg end &optional buf filename)
308 (let ((the-buf (current-buffer)) ret)
310 (setq buf (get-buffer-create mime/decoding-buffer-name))
314 (switch-to-buffer buf)
316 (switch-to-buffer the-buf)
317 (narrow-to-region beg end)
318 (goto-char (point-min))
319 (while (re-search-forward
321 mime/Base64-encoded-text-regexp
323 (setq ret (mime/base64-decode-string
324 (buffer-substring (match-beginning 0)
327 (switch-to-buffer buf)
329 (switch-to-buffer the-buf)
333 (switch-to-buffer buf)
334 (let ((kanji-flag nil)
337 (if (featurep 'mule) *noconv*))
339 (write-file filename)
341 (switch-to-buffer the-buf)
345 (defun mime/start-external-method-region (beg end ctype ctl encoding)
347 (let ((method (cdr (assoc ctype mime/content-decoding-method-alist)))
348 (name (mime/get-name ctl))
352 (search-forward "\n\n" nil t)
353 (let ((file (make-temp-name
354 (expand-file-name "TM" mime/tmp-dir)))
358 (if (and (string= encoding "base64")
359 mime/use-internal-decoder)
361 (mime/base64-decode-region b e nil file)
362 (setq encoding "binary")
364 (write-region b e file)
366 (start-process method mime/output-buffer-name method file
368 (if mime/body-decoding-mode
369 mime/body-decoding-mode
371 (replace-as-filename name)
373 (if (null (get-buffer-window mime/output-buffer-name))
374 (let ((the-buf (current-buffer)))
375 (split-window-vertically (/ (* (window-height) 3) 4))
376 (pop-to-buffer mime/output-buffer-name)
377 (pop-to-buffer the-buf)
381 (defun mime/decode-message/partial-region (beg end ctype default-encoding)
383 (let ((root-dir (expand-file-name
384 (concat "m-prts-" (user-login-name)) mime/tmp-dir))
385 (id (cdr (assoc "id" ctype)))
386 (number (cdr (assoc "number" ctype)))
387 (total (cdr (assoc "total" ctype)))
388 (the-buf (current-buffer))
390 (if (not (file-exists-p root-dir))
391 (shell-command (concat "mkdir " root-dir))
393 (setq id (replace-as-filename id))
394 (setq root-dir (concat root-dir "/" id))
395 (if (not (file-exists-p root-dir))
396 (shell-command (concat "mkdir " root-dir))
398 (setq file (concat root-dir "/FULL"))
399 (if (not (file-exists-p file))
401 (re-search-forward "^$")
402 (goto-char (+ (match-end 0) 1))
403 (setq file (concat root-dir "/" number))
404 (write-region (point) (point-max) file)
405 (if (get-buffer "*MIME-temp*")
406 (kill-buffer "*MIME-temp*")
408 (switch-to-buffer "*MIME-temp*")
410 (max (string-to-int total))
414 (setq file (concat root-dir "/" (int-to-string i)))
415 (if (not (file-exists-p file))
417 (switch-to-buffer the-buf)
420 (insert-file-contents file)
421 (goto-char (point-max))
424 (write-file (concat root-dir "/FULL"))
425 (delete-other-windows)
426 (pop-to-buffer (current-buffer))
427 (goto-char (point-min))
428 (mime/show-message-mode the-buf)
432 (delete-other-windows)
434 (mime/show-message-mode the-buf)
438 (defun mime/decode-content-region (beg end)
442 (outline-flag-region beg end ?\n)
444 (if (< end (point-max))
447 (narrow-to-region beg e)
449 (let ((ctl (mime/Content-Type)))
451 (let ((ctype (downcase (car ctl)))
452 (encoding (mime/Content-Transfer-Encoding "7bit"))
455 (cond ((string= ctype "message/partial")
456 (mime/decode-message/partial-region beg e
459 (t (mime/start-external-method-region beg e
463 mime/default-showing-Content-Type-list))
464 (mime/hide-region beg end)
474 (defun mime/hide-region (beg end)
478 (if (not mime/hide-content-header)
480 (search-forward "\n\n" nil t)
481 (setq beg (match-end 0))
483 (outline-flag-region beg end ?\^M)
486 (defun mime/hide-all ()
487 (let ((fl (mime/make-flat-content-list))
490 (setq p (car (car fl)))
491 (setq c (mime/get-content-region (mime/get-point-content-number p)))
495 (narrow-to-region (car c)(nth 1 c))
497 (let ((ctl (mime/Content-Type)))
501 mime/default-showing-Content-Type-list)))
502 (mime/hide-region (car c)(nth 1 c))
508 ;;; @ MIME show message mode (major-mode)
510 (defun mime/show-message-mode (mother)
511 (kill-all-local-variables)
512 (make-variable-buffer-local 'mime/show-mode-old-window-configuration)
513 (setq mime/show-mode-old-window-configuration
514 (current-window-configuration))
515 (make-variable-buffer-local 'mime/mother-buffer)
516 (setq mime/mother-buffer mother)
517 (setq major-mode 'mime/show-message-mode)
518 (setq mode-name "MIME-View")
520 (run-hooks 'mime/show-message-mode-hook)
524 ;;; @ MIME view message mode (minor-mode)
527 (defun mime/view-mode ()
529 (make-local-variable 'mime/view-mode-old-local-map)
530 (let ((keymap (current-local-map)))
532 (setq keymap (make-sparse-keymap))
534 (setq mime/view-mode-old-local-map keymap)
535 (setq keymap (copy-keymap keymap))
537 (let ((buf (get-buffer mime/output-buffer-name)))
539 (let ((the-buf (current-buffer)))
540 (switch-to-buffer buf)
542 (switch-to-buffer the-buf)
544 (use-local-map keymap)
545 (define-key keymap "u" 'mime/up-content)
546 (define-key keymap "p" 'mime/previous-content)
547 (define-key keymap "n" 'mime/next-content)
548 (define-key keymap " " 'mime/scroll-up-content)
549 (define-key keymap "\M- " 'mime/scroll-down-content)
550 (define-key keymap "v" 'mime/play-content)
551 (define-key keymap "e" 'mime/extract-content)
552 (define-key keymap "\C-c\C-p" 'mime/print-content)
553 (define-key keymap "\C-c\C-x" 'mime/exit-view-mode)
556 (search-forward "\n\n" nil t)
559 (defun mime/decode-content ()
561 (let ((cr (mime/get-content-region
562 (mime/get-point-content-number (point))))
566 (mime/decode-content-region (car cr)(nth 1 cr))
569 (defun mime/play-content ()
571 (let ((mime/body-decoding-mode "play"))
572 (mime/decode-content)
575 (defun mime/extract-content ()
577 (let ((mime/body-decoding-mode "extract"))
578 (mime/decode-content)
581 (defun mime/print-content ()
583 (let ((mime/body-decoding-mode "print"))
584 (mime/decode-content)
587 (defun mime/up-content ()
589 (let ((cn (mime/get-point-content-number (point)))
592 (and (setq r (assoc major-mode mime/go-to-top-node-method-alist))
595 (if (setq r (mime/get-content-region (butlast cn)))
600 (defun mime/previous-content ()
602 (let* ((fcl (mime/make-flat-content-list))
604 (i (- (length fcl) 1))
608 (if (> p (car (nth i fcl)))
609 (throw 'tag (goto-char (car (nth i fcl))))
615 (defun mime/next-content ()
617 (let ((fcl (mime/make-flat-content-list))
622 (if (< p (car (car fcl)))
623 (throw 'tag (goto-char (car (car fcl))))
629 (defun mime/scroll-up-content ()
631 (let ((fcl (mime/make-flat-content-list))
633 (h (- (window-height) 1))
635 (setq np (or (catch 'tag
637 (if (< p (car (car fcl)))
638 (throw 'tag (car (car fcl)))
648 (defun mime/scroll-down-content ()
650 (let ((fcl (mime/make-flat-content-list))
652 (h (- (window-height) 1))
654 (setq pp (or (let ((i (- (length fcl) 1)))
657 (if (> p (nth 1 (nth i fcl)))
658 (throw 'tag (nth 1 (nth i fcl)))
668 (defun mime/exit-view-mode ()
670 (if (and (boundp 'mime/view-mode-old-local-map)
671 (keymapp mime/view-mode-old-local-map))
672 (use-local-map mime/view-mode-old-local-map)