2 ;;; A MIME viewer for GNU Emacs
4 ;;; by Morioka Tomohiko, 1994/07/13
22 (defconst mime/viewer-RCS-ID
23 "$Id: tm-view.el,v 5.16 1994/10/26 19:03:12 morioka Exp $")
25 (defconst mime/viewer-version (get-version-string mime/viewer-RCS-ID))
31 (defconst mime/tspecials "\000-\040()<>@,;:\\\"/[\093?.=")
32 (defconst mime/token-regexp
33 (concat "[^" mime/tspecials "]*"))
34 (defconst mime/content-type-subtype-regexp
35 (concat mime/token-regexp "/" mime/token-regexp))
36 (defconst mime/content-parameter-value-regexp
38 message/quoted-string-regexp
41 (defconst mime/output-buffer-name "*MIME-out*")
42 (defconst mime/decoding-buffer-name "*MIME-decoding*")
48 (defvar mime/content-decoding-condition
49 ;;(setq mime/content-decoding-condition
50 '(((type . "text/plain")
51 (method "tm-plain" nil 'file 'type 'encoding 'mode 'name))
52 ((type . "text/x-latex")
53 (method "tm-latex" nil 'file 'type 'encoding 'mode 'name))
54 ((type . "audio/basic")
55 (method "tm-au" nil 'file 'type 'encoding 'mode 'name))
57 (method "tm-image" nil 'file 'type 'encoding 'mode 'name))
58 ((type . "image/jpeg")
59 (method "tm-image" nil 'file 'type 'encoding 'mode 'name))
60 ((type . "image/tiff")
61 (method "tm-image" nil 'file 'type 'encoding 'mode 'name))
62 ((type . "image/x-tiff")
63 (method "tm-image" nil 'file 'type 'encoding 'mode 'name))
64 ((type . "image/x-xbm")
65 (method "tm-image" nil 'file 'type 'encoding 'mode 'name))
66 ((type . "image/x-pic")
67 (method "tm-image" nil 'file 'type 'encoding 'mode 'name))
68 ((type . "video/mpeg")
69 (method "tm-mpeg" nil 'file 'type 'encoding 'mode 'name))
70 ((type . "application/octet-stream")
71 (method "tm-file" nil 'file 'type 'encoding 'mode 'name))
72 ;;((type . "message/external-body")
73 ;; (method "xterm" nil
74 ;; "-e" "showexternal"
75 ;; 'file '"access-type" '"name" '"site" '"directory"))
76 ((type . "message/partial")
77 (method . mime/decode-message/partial-region))
79 "-m" "tm" "-x" "-d" "-z" "-e" 'file)(mode . "play"))
82 (defvar mime/content-filter-alist
83 '(("text/plain" . mime/decode-text/plain)))
85 (defvar mime/make-content-subject-function
87 (lambda (cid subj ctype)
89 (format "[%s %s (%s)]\n"
93 (format "%s" (+ num 1))
100 (defvar mime/make-content-header-filter
101 ;;(setq mime/make-content-header-filter
105 (delete-region (goto-char (point-min))
106 (or (and (re-search-forward "^$" nil t)
113 (defvar mime/default-showing-Content-Type-list
114 ;;(setq mime/default-showing-Content-Type-list
115 '("text/plain" "text/richtext" "text/enriched" "text/x-latex" nil))
117 (defvar mime/go-to-top-node-method-alist
118 ;;(setq mime/go-to-top-node-method-alist
119 '((gnus-article-mode . (lambda ()
120 (mime/exit-view-mode)
121 (delete-other-windows)
122 (gnus-article-show-summary)
124 (rmail-mode . (lambda ()
125 (mime/exit-view-mode)
127 (delete-other-windows)
129 (mh-show-mode . (lambda ()
130 (let ((win (get-buffer-window
131 mime/output-buffer-name))
133 (nth 2 (car mime/preview-flat-content-list)))
138 (mime/exit-view-mode)
140 (let ((name (buffer-name buf)))
141 (string-match "show-" name)
142 (substring name (match-end 0))
145 (mime/show-message-mode . (lambda ()
146 (set-window-configuration
147 mime/show-mode-old-window-configuration)
148 (let ((mother mime/mother-buffer))
151 mime/preview-flat-content-list)))
152 (mime/exit-view-mode)
153 (pop-to-buffer mother)
154 (goto-char (point-min))
159 (defvar mime/use-internal-decoder nil)
161 (defvar mime/body-decoding-mode "play" "MIME body decoding mode")
167 (defun mime/parse-contents ()
170 (goto-char (point-min))
171 (let* ((ctl (mime/Content-Type))
173 (boundary (assoc "boundary" (cdr ctl)))
176 (setq ctype (downcase ctype))
178 (search-forward "\n\n" nil t)
180 (let ((sep (concat "\n--"
182 (message/strip-quoted-string
186 (setq beg (match-end 0))
187 (search-forward (concat "\n--" boundary "--\n") nil t)
188 (setq end (match-beginning 0))
191 (narrow-to-region beg end)
192 (goto-char (point-min))
193 (search-forward (concat "--" boundary "\n") nil t)
194 (setq cb (match-end 0))
195 (while (search-forward sep nil t)
196 (setq ce (match-beginning 0))
197 (setq ncb (match-end 0))
200 (narrow-to-region cb ce)
201 (setq ret (mime/parse-contents))
203 (setq dest (nconc dest (list ret)))
204 (goto-char (nth 1 ret))
205 (search-forward (concat "--" boundary "\n") nil t)
206 (goto-char (setq cb (match-end 0)))
208 (setq ce (point-max))
211 (narrow-to-region cb ce)
212 (setq ret (mime/parse-contents))
214 (setq dest (append dest (list ret)))
216 (setq beg (point-min))
218 (search-forward (concat "\n--" boundary "--\n") nil t)
219 (setq end (match-beginning 0))
221 ((string= ctype "message/rfc822")
224 (narrow-to-region (match-end 0) (point-max))
225 (setq dest (list (mime/parse-contents)))
227 (setq beg (point-min))
228 (setq end (point-max))
230 (t (setq beg (point-min))
231 (setq end (point-max))
236 (defun mime/Content-Type ()
239 (if (and (re-search-forward "^Content-Type:[ \t]*" nil t)
243 (and (re-search-forward ".*\\(\n[ \t].*\\)*" nil t)
246 (goto-char (point-min))
247 (re-search-forward mime/content-type-subtype-regexp nil t)
251 (buffer-substring (match-beginning 0) (match-end 0))
253 dest attribute value)
254 (while (and (re-search-forward "[ \t\n]*;[ \t\n]*" nil t)
255 (re-search-forward mime/token-regexp nil t)
259 (buffer-substring (match-beginning 0) (match-end 0))
261 (if (and (re-search-forward "=[ \t\n]*" nil t)
262 (re-search-forward mime/content-parameter-value-regexp
267 (message/strip-quoted-string
268 (buffer-substring (match-beginning 0)
276 (defun mime/Content-Transfer-Encoding (&optional default-encoding)
279 (if (and (re-search-forward "^Content-Transfer-Encoding:[ \t]*" nil t)
280 (re-search-forward mime/token-regexp nil t)
282 (downcase (buffer-substring (match-beginning 0) (match-end 0)))
286 (defun mime/get-subject (param)
290 (or (and (setq ret (assoc "name" param))
291 (message/strip-quoted-string (cdr ret))
293 (and (setq ret (assoc "x-name" param))
294 (message/strip-quoted-string (cdr ret))
297 (narrow-to-region (point-min)
298 (or (and (search-forward "\n\n" nil t)
303 (message/get-field-body "Content-Description")
304 (message/get-field-body "Subject")
309 (defun mime/get-name (param)
310 (replace-as-filename (mime/get-subject param))
313 (defun mime/make-preview-buffer (&optional buf cl obuf)
314 (let ((the-buf (current-buffer)) fcl)
316 (setq buf (current-buffer))
317 (setq buf (get-buffer buf))
321 (switch-to-buffer buf)
322 (setq cl mime/content-list)
325 (setq obuf (concat "*Preview-" (buffer-name buf) "*"))
327 (setq fcl (mime/make-flat-content-list cl))
328 (if (get-buffer obuf)
331 (let ((r fcl) cell cid ctype beg end e nb ne subj dest str)
334 (setq beg (car cell))
335 (setq end (nth 1 cell))
336 (setq cid (mime/get-point-content-number beg cl))
337 (switch-to-buffer buf)
340 (narrow-to-region beg end)
342 (setq ctype (mime/Content-Type))
344 (if (not (member (car ctype)
345 mime/default-showing-Content-Type-list))
348 (search-forward "\n\n" nil t)
354 (if (> e (point-max))
357 (setq str (buffer-substring beg e))
358 (switch-to-buffer obuf)
364 (narrow-to-region nb ne)
365 (mime/decode-message-header)
366 (setq subj (mime/get-subject (cdr ctype)))
367 (let ((f (cdr (assoc (car ctype) mime/content-filter-alist))))
368 (if (and f (fboundp f))
371 (funcall mime/make-content-header-filter cid)
373 (funcall mime/make-content-subject-function cid subj ctype)
374 (setq ne (point-max))
375 (setq dest (nconc dest (list (list nb (- ne 1) buf beg end))))
380 (set-buffer-modified-p nil)
381 (setq buffer-read-only t)
382 (switch-to-buffer the-buf)
386 (defun mime/parse-message ()
388 (make-variable-buffer-local 'mime/content-list)
389 (setq mime/content-list (mime/parse-contents))
390 (let ((ret (mime/make-preview-buffer)))
391 (make-variable-buffer-local 'mime/preview-buffer)
392 (setq mime/preview-buffer (car ret))
395 ;;; @ content information
398 (defun mime/get-point-content-number (p &optional cl)
400 (setq cl mime/content-list)
406 (if (and (<= b p)(<= p e))
407 (or (let (co ret (sn 0))
411 (setq ret (mime/get-point-content-number p co))
412 (cond ((eq ret t) (throw 'tag (list sn)))
413 (ret (throw 'tag (cons sn ret)))
420 (defun mime/get-content-region (cn &optional cl)
422 (setq cl mime/content-list)
429 (let ((rcl (nth sn (nth 2 cl))))
431 (mime/get-content-region (cdr cn) rcl)
435 (defun mime/make-flat-content-list (&optional cl)
437 (setq cl mime/content-list)
439 (let ((dest (list cl))
443 (setq dest (append dest (mime/make-flat-content-list (car rcl))))
448 (defun mime/get-point-preview-content (p &optional fcl)
450 (setq fcl mime/preview-flat-content-list)
456 (if (and (<= (car cell) p)(<= p (nth 1 cell)))
468 (defun mime/Quoted-Printable-decode-region (beg end)
472 (narrow-to-region beg end)
473 (goto-char (point-min))
474 (while (re-search-forward "=\n" nil t)
477 (goto-char (point-min))
479 (while (re-search-forward mime/Quoted-Printable-octet-regexp nil t)
480 (setq b (match-beginning 0))
481 (setq e (match-end 0))
482 (setq str (buffer-substring b e))
484 (insert (mime/Quoted-Printable-decode-string str))
488 (defun mime/Base64-decode-region (beg end)
492 (narrow-to-region beg end)
493 (goto-char (point-min))
494 (while (search-forward "\n" nil t)
497 (let ((str (buffer-substring (point-min)(point-max))))
498 (delete-region (point-min)(point-max))
499 (insert (mime/base64-decode-string str))
502 (defun mime/make-method-args (cal format)
507 (let ((ret (cdr (assoc (eval arg) cal))))
515 (defun mime/start-external-method-region (beg end cal)
517 (if (< end (point-max))
522 (narrow-to-region beg e)
524 (let ((method (cdr (assoc 'method cal)))
525 (name (mime/get-name cal))
528 (let ((file (make-temp-name
529 (expand-file-name "TM" mime/tmp-dir)))
533 (search-forward "\n\n" nil t)
534 (setq b (match-end 0))
537 (write-region b e file)
539 'name (replace-as-filename name) cal))
540 (setq cal (put-alist 'file file cal))
543 mime/output-buffer-name (car method)
545 (mime/make-method-args cal (cdr (cdr method)))
547 (apply (function start-process) args)
548 (mime/show-output-buffer)
552 (defun mime/decode-message/partial-region (beg end cal)
554 (let* ((root-dir (expand-file-name
555 (concat "m-prts-" (user-login-name)) mime/tmp-dir))
556 (id (cdr (assoc "id" cal)))
557 (number (cdr (assoc "number" cal)))
558 (total (cdr (assoc "total" cal)))
559 (the-buf (current-buffer))
561 (mother mime/preview-buffer))
562 (if (not (file-exists-p root-dir))
563 (shell-command (concat "mkdir " root-dir))
565 (setq id (replace-as-filename id))
566 (setq root-dir (concat root-dir "/" id))
567 (if (not (file-exists-p root-dir))
568 (shell-command (concat "mkdir " root-dir))
570 (setq file (concat root-dir "/FULL"))
571 (if (not (file-exists-p file))
573 (re-search-forward "^$")
574 (goto-char (+ (match-end 0) 1))
575 (setq file (concat root-dir "/" number))
576 (write-region (point) (point-max) file)
577 (if (get-buffer "*MIME-temp*")
578 (kill-buffer "*MIME-temp*")
580 (switch-to-buffer "*MIME-temp*")
582 (max (string-to-int total))
586 (setq file (concat root-dir "/" (int-to-string i)))
587 (if (not (file-exists-p file))
589 (switch-to-buffer the-buf)
592 (insert-file-contents file)
593 (goto-char (point-max))
596 (delete-other-windows)
597 (write-file (concat root-dir "/FULL"))
598 (setq major-mode 'mime/show-message-mode)
599 (mime/viewer-mode mother)
600 (pop-to-buffer (current-buffer))
604 (delete-other-windows)
606 (setq major-mode 'mime/show-message-mode)
607 (mime/viewer-mode mother)
608 (pop-to-buffer (current-buffer))
612 (defun mime/get-content-decoding-alist (al)
613 (let ((r mime/content-decoding-condition) ret)
616 (if (setq ret (nth 1 (assoc-unify (car r) al)))
622 (defun mime/decode-content-region (beg end)
627 (narrow-to-region beg end)
629 (setq ctl (mime/Content-Type))
631 (setq encoding (mime/Content-Transfer-Encoding "7bit"))
634 (let ((ctype (downcase (car ctl))) method cal ret)
636 (setq cal (nconc (list (cons 'type ctype)
637 (cons 'encoding encoding)
640 (if mime/body-decoding-mode
642 (cons 'mode mime/body-decoding-mode)
645 (setq ret (mime/get-content-decoding-alist cal))
646 (setq method (cdr (assoc 'method ret)))
647 (cond ((and (symbolp method)
649 (funcall method beg end ret)
651 ((and (listp method)(stringp (car method)))
652 (mime/start-external-method-region beg end ret)
654 (t (mime/show-output-buffer
655 "No method are specified for %s\n" ctype)
660 (defun mime/show-output-buffer (&rest forms)
661 (let ((the-buf (current-buffer)))
662 (if (null (get-buffer-window mime/output-buffer-name))
663 (split-window-vertically (/ (* (window-height) 3) 4))
665 (pop-to-buffer mime/output-buffer-name)
666 (goto-char (point-max))
668 (insert (apply (function format) forms))
670 (pop-to-buffer the-buf)
677 (defun mime/decode-text/plain (ctl)
681 (let ((charset (cdr (assoc "charset" (cdr ctl))))
685 (goto-char (point-min))
686 (narrow-to-region (point-min)
687 (or (and (search-forward "\n\n" nil t)
690 (goto-char (point-min))
691 (mime/Content-Transfer-Encoding "7bit")
693 (beg (point-min)) (end (point-max))
695 (goto-char (point-min))
696 (if (search-forward "\n\n" nil t)
697 (setq beg (match-end 0))
699 (if (cond ((string= encoding "quoted-printable")
700 (mime/Quoted-Printable-decode-region beg end)
702 ((string= encoding "base64")
703 (mime/Base64-decode-region beg end)
705 (mime/code-convert-region-to-emacs beg (point-max) charset)
710 ;;; @ MIME viewer mode
713 (defun mime/viewer-mode (&optional mother)
715 (let ((buf (get-buffer mime/output-buffer-name))
716 (the-buf (current-buffer))
720 (switch-to-buffer buf)
722 (switch-to-buffer the-buf)
724 (let ((ret (mime/parse-message))
726 (switch-to-buffer (car ret))
727 (setq major-mode 'mime/viewer-mode)
728 (setq mode-name "MIME-View")
730 (make-variable-buffer-local 'mime/viewer-original-major-mode)
731 (setq mime/viewer-original-major-mode
734 (make-variable-buffer-local
735 'mime/show-mode-old-window-configuration)
736 (setq mime/show-mode-old-window-configuration
737 (current-window-configuration))
738 (make-variable-buffer-local 'mime/mother-buffer)
739 (setq mime/mother-buffer mother)
740 'mime/show-message-mode)
742 (let ((keymap (current-local-map)))
744 (setq keymap (make-sparse-keymap))
745 (setq keymap (copy-keymap keymap))
747 (use-local-map keymap)
748 (define-key keymap "u" 'mime/up-content)
749 (define-key keymap "p" 'mime/previous-content)
750 (define-key keymap "n" 'mime/next-content)
751 (define-key keymap " " 'mime/scroll-up-content)
752 (define-key keymap "\M- " 'mime/scroll-down-content)
753 (define-key keymap "\177" 'mime/scroll-down-content)
754 (define-key keymap "\C-m" 'mime/next-line-content)
755 (define-key keymap "\C-\M-m" 'mime/previous-line-content)
756 (define-key keymap "v" 'mime/play-content)
757 (define-key keymap "e" 'mime/extract-content)
758 (define-key keymap "\C-c\C-p" 'mime/print-content)
759 (define-key keymap "\C-c\C-x" 'mime/exit-view-mode)
761 (make-variable-buffer-local 'mime/preview-flat-content-list)
762 (setq mime/preview-flat-content-list (nth 1 ret))
765 (let ((ce (nth 1 (car mime/preview-flat-content-list)))
767 (goto-char (point-min))
768 (search-forward "\n\n" nil t)
769 (setq e (match-end 0))
775 (defun mime/decode-content ()
777 (let ((pc (mime/get-point-preview-content (point))))
779 (let ((the-buf (current-buffer)))
780 (switch-to-buffer (nth 2 pc))
781 (mime/decode-content-region (nth 3 pc)(nth 4 pc))
782 (if (eq (current-buffer) (nth 2 pc))
783 (switch-to-buffer the-buf)
787 (defun mime/play-content ()
789 (let ((mime/body-decoding-mode "play"))
790 (mime/decode-content)
793 (defun mime/extract-content ()
795 (let ((mime/body-decoding-mode "extract"))
796 (mime/decode-content)
799 (defun mime/print-content ()
801 (let ((mime/body-decoding-mode "print"))
802 (mime/decode-content)
805 (defun mime/up-content ()
807 (let ((pc (mime/get-point-preview-content (point)))
808 (the-buf (current-buffer))
810 (switch-to-buffer (nth 2 pc))
811 (setq cn (mime/get-point-content-number (nth 3 pc)))
813 (if (setq r (assoc major-mode mime/go-to-top-node-method-alist))
815 (switch-to-buffer the-buf)
818 (setq r (mime/get-content-region (butlast cn)))
819 (switch-to-buffer the-buf)
821 (let ((rfcl mime/preview-flat-content-list) cell)
823 (setq cell (car rfcl))
824 (if (and (= (car r)(nth 3 cell))
825 (= (nth 1 r)(nth 4 cell))
828 (goto-char (nth 0 cell))
831 (setq rfcl (cdr rfcl))
835 (defun mime/previous-content ()
837 (let* ((fcl mime/preview-flat-content-list)
839 (i (- (length fcl) 1))
843 (if (> p (car (nth i fcl)))
844 (throw 'tag (goto-char (car (nth i fcl))))
850 (defun mime/next-content ()
852 (let ((fcl mime/preview-flat-content-list)
857 (if (< p (car (car fcl)))
858 (throw 'tag (goto-char (car (car fcl))))
864 (defun mime/scroll-up-content (&optional h)
867 (setq h (- (window-height) 1))
869 (let ((fcl mime/preview-flat-content-list)
872 (setq np (or (catch 'tag
874 (if (< p (car (car fcl)))
875 (throw 'tag (car (car fcl)))
885 (defun mime/scroll-down-content (&optional h)
888 (setq h (- (window-height) 1))
890 (let ((fcl mime/preview-flat-content-list)
893 (setq pp (or (let ((i (- (length fcl) 1)))
896 (if (> p (nth 1 (nth i fcl)))
897 (throw 'tag (nth 1 (nth i fcl)))
907 (defun mime/next-line-content ()
909 (mime/scroll-up-content 1)
912 (defun mime/previous-line-content ()
914 (mime/scroll-down-content 1)
917 (defun mime/exit-view-mode ()
919 (kill-buffer (current-buffer))
922 (fset 'mime/view-mode 'mime/viewer-mode)
924 (run-hooks 'tm-view-load-hook)