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.19 1994/11/08 11:13: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 (get-unified-alist mime/content-decoding-condition al)
616 (defun mime/decode-content-region (beg end)
621 (narrow-to-region beg end)
623 (setq ctl (mime/Content-Type))
625 (setq encoding (mime/Content-Transfer-Encoding "7bit"))
628 (let ((ctype (downcase (car ctl))) method cal ret)
630 (setq cal (nconc (list (cons 'type ctype)
631 (cons 'encoding encoding)
634 (if mime/body-decoding-mode
636 (cons 'mode mime/body-decoding-mode)
639 (setq ret (mime/get-content-decoding-alist cal))
640 (setq method (cdr (assoc 'method ret)))
641 (cond ((and (symbolp method)
643 (funcall method beg end ret)
645 ((and (listp method)(stringp (car method)))
646 (mime/start-external-method-region beg end ret)
648 (t (mime/show-output-buffer
649 "No method are specified for %s\n" ctype)
654 (defun mime/show-output-buffer (&rest forms)
655 (let ((the-buf (current-buffer)))
656 (if (null (get-buffer-window mime/output-buffer-name))
657 (split-window-vertically (/ (* (window-height) 3) 4))
659 (pop-to-buffer mime/output-buffer-name)
660 (goto-char (point-max))
662 (insert (apply (function format) forms))
664 (pop-to-buffer the-buf)
671 (defun mime/decode-text/plain (ctl)
675 (let ((charset (cdr (assoc "charset" (cdr ctl))))
679 (goto-char (point-min))
680 (narrow-to-region (point-min)
681 (or (and (search-forward "\n\n" nil t)
684 (goto-char (point-min))
685 (mime/Content-Transfer-Encoding "7bit")
687 (beg (point-min)) (end (point-max))
689 (goto-char (point-min))
690 (if (search-forward "\n\n" nil t)
691 (setq beg (match-end 0))
693 (if (cond ((string= encoding "quoted-printable")
694 (mime/Quoted-Printable-decode-region beg end)
696 ((string= encoding "base64")
697 (mime/Base64-decode-region beg end)
699 (mime/code-convert-region-to-emacs beg (point-max) charset)
704 ;;; @ MIME viewer mode
707 (defvar mime/viewer-mode-map nil)
708 (if (null mime/viewer-mode-map)
710 (setq mime/viewer-mode-map (make-keymap))
711 (suppress-keymap mime/viewer-mode-map)
712 (define-key mime/viewer-mode-map "u" 'mime/up-content)
713 (define-key mime/viewer-mode-map "p" 'mime/previous-content)
714 (define-key mime/viewer-mode-map "n" 'mime/next-content)
715 (define-key mime/viewer-mode-map " " 'mime/scroll-up-content)
716 (define-key mime/viewer-mode-map "\M- " 'mime/scroll-down-content)
717 (define-key mime/viewer-mode-map "\177" 'mime/scroll-down-content)
718 (define-key mime/viewer-mode-map "\C-m" 'mime/next-line-content)
719 (define-key mime/viewer-mode-map "\C-\M-m" 'mime/previous-line-content)
720 (define-key mime/viewer-mode-map "v" 'mime/play-content)
721 (define-key mime/viewer-mode-map "e" 'mime/extract-content)
722 (define-key mime/viewer-mode-map "\C-c\C-p" 'mime/print-content)
723 (define-key mime/viewer-mode-map "q" 'mime/quit-view-mode)
724 (define-key mime/viewer-mode-map "\C-c\C-x" 'mime/exit-view-mode)
727 (defun mime/viewer-mode (&optional mother)
728 "Major mode for viewing MIME message.
730 u Move to upper content
731 p Move to previous content
732 n Move to next content
736 RET Move to next line
737 M-RET Move to previous line
738 v Decode the content as `play mode'
739 e Decode the content as `extract mode'
740 C-c C-p Decode the content as `print mode'
744 (let ((buf (get-buffer mime/output-buffer-name))
745 (the-buf (current-buffer))
749 (switch-to-buffer buf)
751 (switch-to-buffer the-buf)
753 (let ((ret (mime/parse-message))
755 (switch-to-buffer (car ret))
756 (setq major-mode 'mime/viewer-mode)
757 (setq mode-name "MIME-View")
758 (make-variable-buffer-local 'mime/viewer-original-major-mode)
759 (setq mime/viewer-original-major-mode
762 (make-variable-buffer-local
763 'mime/show-mode-old-window-configuration)
764 (setq mime/show-mode-old-window-configuration
765 (current-window-configuration))
766 (make-variable-buffer-local 'mime/mother-buffer)
767 (setq mime/mother-buffer mother)
768 'mime/show-message-mode)
770 (use-local-map mime/viewer-mode-map)
771 (make-variable-buffer-local 'mime/preview-flat-content-list)
772 (setq mime/preview-flat-content-list (nth 1 ret))
774 (let ((ce (nth 1 (car mime/preview-flat-content-list)))
776 (goto-char (point-min))
777 (search-forward "\n\n" nil t)
778 (setq e (match-end 0))
782 (run-hooks 'mime/viewer-mode-hook)
785 (defun mime/decode-content ()
787 (let ((pc (mime/get-point-preview-content (point))))
789 (let ((the-buf (current-buffer)))
790 (switch-to-buffer (nth 2 pc))
791 (mime/decode-content-region (nth 3 pc)(nth 4 pc))
792 (if (eq (current-buffer) (nth 2 pc))
793 (switch-to-buffer the-buf)
797 (defun mime/play-content ()
799 (let ((mime/body-decoding-mode "play"))
800 (mime/decode-content)
803 (defun mime/extract-content ()
805 (let ((mime/body-decoding-mode "extract"))
806 (mime/decode-content)
809 (defun mime/print-content ()
811 (let ((mime/body-decoding-mode "print"))
812 (mime/decode-content)
815 (defun mime/up-content ()
817 (let ((pc (mime/get-point-preview-content (point)))
818 (the-buf (current-buffer))
820 (switch-to-buffer (nth 2 pc))
821 (setq cn (mime/get-point-content-number (nth 3 pc)))
823 (mime/quit-view-mode the-buf (nth 2 pc))
824 (setq r (mime/get-content-region (butlast cn)))
825 (switch-to-buffer the-buf)
827 (let ((rfcl mime/preview-flat-content-list) cell)
829 (setq cell (car rfcl))
830 (if (and (= (car r)(nth 3 cell))
831 (= (nth 1 r)(nth 4 cell))
834 (goto-char (nth 0 cell))
837 (setq rfcl (cdr rfcl))
841 (defun mime/previous-content ()
843 (let* ((fcl mime/preview-flat-content-list)
845 (i (- (length fcl) 1))
849 (if (> p (car (nth i fcl)))
850 (throw 'tag (goto-char (car (nth i fcl))))
856 (defun mime/next-content ()
858 (let ((fcl mime/preview-flat-content-list)
863 (if (< p (car (car fcl)))
864 (throw 'tag (goto-char (car (car fcl))))
870 (defun mime/scroll-up-content (&optional h)
873 (setq h (- (window-height) 1))
875 (let ((fcl mime/preview-flat-content-list)
878 (setq np (or (catch 'tag
880 (if (< p (car (car fcl)))
881 (throw 'tag (car (car fcl)))
891 (defun mime/scroll-down-content (&optional h)
894 (setq h (- (window-height) 1))
896 (let ((fcl mime/preview-flat-content-list)
899 (setq pp (or (let ((i (- (length fcl) 1)))
902 (if (> p (nth 1 (nth i fcl)))
903 (throw 'tag (nth 1 (nth i fcl)))
913 (defun mime/next-line-content ()
915 (mime/scroll-up-content 1)
918 (defun mime/previous-line-content ()
920 (mime/scroll-down-content 1)
923 (defun mime/quit-view-mode (&optional the-buf buf)
926 (setq the-buf (current-buffer))
929 (setq buf (nth 2 (mime/get-point-preview-content (point))))
932 (switch-to-buffer buf)
933 (assoc major-mode mime/go-to-top-node-method-alist)
937 (switch-to-buffer the-buf)
942 (defun mime/exit-view-mode ()
944 (kill-buffer (current-buffer))
947 (fset 'mime/view-mode 'mime/viewer-mode)
949 (run-hooks 'tm-view-load-hook)