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 5.1 1994/09/25 21:23:07 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))
33 (defconst mime/tspecials "\000-\040()<>@,;:\\\"/[\093?.=")
34 (defconst mime/token-regexp
35 (concat "[^" mime/tspecials "]*"))
36 (defconst mime/content-type-subtype-regexp
37 (concat mime/token-regexp "/" mime/token-regexp))
38 (defconst mime/content-parameter-value-regexp
40 message/quoted-string-regexp
43 (defconst mime/output-buffer-name "*MIME-out*")
44 (defconst mime/decoding-buffer-name "*MIME-decoding*")
50 (defvar mime/content-decoding-condition
51 ;;(setq mime/content-decoding-condition
52 '(((type . "text/plain")
53 (method "tm-plain" nil 'file 'type 'encoding 'mode 'name))
54 ((type . "text/x-latex")
55 (method "tm-latex" nil 'file 'type 'encoding 'mode 'name))
56 ((type . "audio/basic")
57 (method "tm-au" nil 'file 'type 'encoding 'mode 'name))
59 (method "tm-image" nil 'file 'type 'encoding 'mode 'name))
60 ((type . "image/jpeg")
61 (method "tm-image" nil 'file 'type 'encoding 'mode 'name))
62 ((type . "image/tiff")
63 (method "tm-image" nil 'file 'type 'encoding 'mode 'name))
64 ((type . "image/x-tiff")
65 (method "tm-image" nil 'file 'type 'encoding 'mode 'name))
66 ((type . "image/x-xbm")
67 (method "tm-image" nil 'file 'type 'encoding 'mode 'name))
68 ((type . "image/x-pic")
69 (method "tm-image" nil 'file 'type 'encoding 'mode 'name))
70 ((type . "video/mpeg")
71 (method "tm-mpeg" nil 'file 'type 'encoding 'mode 'name))
72 ((type . "application/octet-stream")
73 (method "tm-file" nil 'file 'type 'encoding 'mode 'name))
74 ;;((type . "message/external-body")
75 ;; (method "xterm" nil
76 ;; "-e" "showexternal"
77 ;; 'file '"access-type" '"name" '"site" '"directory"))
78 ((type . "message/partial")
79 (method . mime/decode-message/partial-region))
81 "-m" "tm" "-x" "-d" "-z" "-e" 'file)(mode . "play"))
84 (defvar mime/content-filter-alist nil)
86 (defvar mime/make-content-subject-function
88 (lambda (cid subj ctype)
90 (format "[%s %s (%s)]\n"
94 (format "%s" (+ num 1))
101 (defvar mime/make-content-header-filter
105 (delete-region (goto-char (point-min))
106 (or (and (search-forward "\n\n" 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"
116 "text/x-latex" "message/rfc822" nil))
118 (defvar mime/go-to-top-node-method-alist
119 ;;(setq mime/go-to-top-node-method-alist
120 '((gnus-article-mode . (lambda ()
121 (mime/exit-view-mode)
122 (delete-other-windows)
123 (gnus-article-show-summary)
125 (rmail-mode . (lambda ()
126 (mime/exit-view-mode)
128 (delete-other-windows)
130 (mh-show-mode . (lambda ()
131 (let ((win (get-buffer-window
132 mime/output-buffer-name))
134 (nth 2 (car mime/preview-flat-content-list)))
139 (mime/exit-view-mode)
141 (let ((name (buffer-name buf)))
142 (string-match "show-" name)
143 (substring name (match-end 0))
146 (mime/show-message-mode . (lambda ()
147 (set-window-configuration
148 mime/show-mode-old-window-configuration)
149 (let ((mother mime/mother-buffer))
152 mime/preview-flat-content-list)))
153 (mime/exit-view-mode)
154 (pop-to-buffer mother)
155 (goto-char (point-min))
160 (defvar mime/tmp-dir "/tmp/")
162 (defvar mime/use-internal-decoder nil)
164 (defvar mime/body-decoding-mode "play" "MIME body decoding mode")
170 (defun mime/parse-contents ()
173 (goto-char (point-min))
174 (let* ((ctl (mime/Content-Type))
176 (boundary (assoc "boundary" (cdr ctl)))
179 (setq ctype (downcase ctype))
181 (search-forward "\n\n" nil t)
183 (let ((sep (concat "\n--"
185 (message/strip-quoted-string
189 (setq beg (match-end 0))
190 (search-forward (concat "\n--" boundary "--\n") nil t)
191 (setq end (match-beginning 0))
194 (narrow-to-region beg end)
195 (goto-char (point-min))
196 (search-forward (concat "--" boundary "\n") nil t)
197 (setq cb (match-end 0))
198 (while (search-forward sep nil t)
199 (setq ce (match-beginning 0))
200 (setq ncb (match-end 0))
203 (narrow-to-region cb ce)
204 (setq ret (mime/parse-contents))
206 (setq dest (nconc dest (list ret)))
207 (goto-char (nth 1 ret))
208 (search-forward (concat "--" boundary "\n") nil t)
209 (goto-char (setq cb (match-end 0)))
211 (setq ce (point-max))
214 (narrow-to-region cb ce)
215 (setq ret (mime/parse-contents))
217 (setq dest (append dest (list ret)))
219 (setq beg (point-min))
221 (search-forward (concat "\n--" boundary "--\n") nil t)
222 (setq end (match-beginning 0))
224 ((string= ctype "message/rfc822")
227 (narrow-to-region (match-end 0) (point-max))
228 (setq dest (list (mime/parse-contents)))
230 (setq beg (point-min))
231 (setq end (point-max))
233 (t (setq beg (point-min))
234 (setq end (point-max))
239 (defun mime/Content-Type ()
242 (if (and (re-search-forward "^Content-Type:[ \t]*" nil t)
246 (and (re-search-forward ".*\\(\n[ \t].*\\)*" nil t)
249 (goto-char (point-min))
250 (re-search-forward mime/content-type-subtype-regexp nil t)
254 (buffer-substring (match-beginning 0) (match-end 0))
256 dest attribute value)
257 (while (and (re-search-forward "[ \t\n]*;[ \t\n]*" nil t)
258 (re-search-forward mime/token-regexp nil t)
262 (buffer-substring (match-beginning 0) (match-end 0))
264 (if (and (re-search-forward "=[ \t\n]*" nil t)
265 (re-search-forward mime/content-parameter-value-regexp
270 (message/strip-quoted-string
271 (buffer-substring (match-beginning 0)
279 (defun mime/Content-Transfer-Encoding (&optional default-encoding)
282 (if (and (re-search-forward "^Content-Transfer-Encoding:[ \t]*" nil t)
283 (re-search-forward mime/token-regexp nil t)
285 (downcase (buffer-substring (match-beginning 0) (match-end 0)))
289 (defun mime/get-subject (param)
293 (or (and (setq ret (assoc "name" param))
294 (message/strip-quoted-string (cdr ret))
296 (and (setq ret (assoc "x-name" param))
297 (message/strip-quoted-string (cdr ret))
299 (message/get-field-body "Content-Description")
300 (message/get-field-body "Subject")
304 (defun mime/get-name (param)
305 (replace-as-filename (mime/get-subject param))
308 (defun mime/make-preview-buffer (&optional buf cl obuf)
309 (let ((the-buf (current-buffer)) fcl)
311 (setq buf (current-buffer))
312 (setq buf (get-buffer buf))
316 (switch-to-buffer buf)
317 (setq cl mime/content-list)
320 (setq obuf (concat "*Preview-" (buffer-name buf) "*"))
322 (setq fcl (mime/make-flat-content-list cl))
323 (if (get-buffer obuf)
325 (switch-to-buffer obuf)
328 (let ((r fcl) cell cid ctype beg end e nb ne subj dest)
331 (setq beg (car cell))
332 (setq end (nth 1 cell))
333 (setq cid (mime/get-point-content-number beg cl))
334 (switch-to-buffer buf)
337 (narrow-to-region beg end)
339 (setq ctype (mime/Content-Type))
341 (if (not (member (car ctype)
342 mime/default-showing-Content-Type-list))
345 (search-forward "\n\n" nil t)
350 (setq str (buffer-substring beg e))
351 (switch-to-buffer obuf)
354 (setq ne (- (point) 1))
357 (narrow-to-region nb ne)
358 (mime/decode-message-header)
359 (setq subj (mime/get-subject (cdr ctype)))
360 (let ((f (cdr (assoc (car ctype) mime/content-filter-alist))))
361 (if (and f (fboundp f))
364 (funcall mime/make-content-header-filter cid)
366 (funcall mime/make-content-subject-function cid subj ctype)
367 (setq ne (point-max))
368 (setq dest (nconc dest (list (list nb ne buf beg end))))
372 (set-buffer-modified-p nil)
373 (switch-to-buffer the-buf)
377 (defun mime/parse-message ()
379 (make-variable-buffer-local 'mime/content-list)
380 (setq mime/content-list (mime/parse-contents))
381 (let ((ret (mime/make-preview-buffer)))
382 (make-variable-buffer-local 'mime/preview-buffer)
383 (setq mime/preview-buffer (car ret))
386 ;;; @ content information
389 (defun mime/get-point-content-number (p &optional cl)
391 (setq cl mime/content-list)
397 (if (and (<= b p)(<= p e))
398 (or (let (co ret (sn 0))
402 (setq ret (mime/get-point-content-number p co))
403 (cond ((eq ret t) (throw 'tag (list sn)))
404 (ret (throw 'tag (cons sn ret)))
411 (defun mime/get-content-region (cn &optional cl)
413 (setq cl mime/content-list)
420 (let ((rcl (nth sn (nth 2 cl))))
422 (mime/get-content-region (cdr cn) rcl)
426 (defun mime/make-flat-content-list (&optional cl)
428 (setq cl mime/content-list)
430 (let ((dest (list cl))
434 (setq dest (append dest (mime/make-flat-content-list (car rcl))))
439 (defun mime/get-point-preview-content (p &optional fcl)
441 (setq fcl mime/preview-flat-content-list)
447 (if (and (<= (car cell) p)(<= p (nth 1 cell)))
459 (defun mime/base64-decode-region (beg end &optional buf filename)
460 (let ((the-buf (current-buffer)) ret)
462 (setq buf (get-buffer-create mime/decoding-buffer-name))
466 (switch-to-buffer buf)
468 (switch-to-buffer the-buf)
469 (narrow-to-region beg end)
470 (goto-char (point-min))
471 (while (re-search-forward
473 mime/Base64-encoded-text-regexp
475 (setq ret (mime/base64-decode-string
476 (buffer-substring (match-beginning 0)
479 (switch-to-buffer buf)
481 (switch-to-buffer the-buf)
485 (switch-to-buffer buf)
486 (let ((kanji-flag nil)
489 (if (featurep 'mule) *noconv*))
491 (write-file filename)
493 (switch-to-buffer the-buf)
497 (defun mime/make-method-args (cal format)
502 (let ((ret (cdr (assoc (eval arg) cal))))
510 (defun mime/start-external-method-region (beg end cal)
512 (if (< end (point-max))
517 (narrow-to-region beg e)
519 (let ((method (cdr (assoc 'method cal)))
520 (name (mime/get-name cal))
523 (let ((file (make-temp-name
524 (expand-file-name "TM" mime/tmp-dir)))
528 (search-forward "\n\n" nil t)
529 (setq b (match-end 0))
532 (write-region b e file)
534 'name (replace-as-filename name) cal))
535 (setq cal (put-alist 'file file cal))
538 mime/output-buffer-name (car method)
540 (mime/make-method-args cal (cdr (cdr method)))
542 (apply (function start-process) args)
543 (mime/show-output-buffer)
547 (defun mime/decode-message/partial-region (beg end cal)
549 (let* ((root-dir (expand-file-name
550 (concat "m-prts-" (user-login-name)) mime/tmp-dir))
551 (id (cdr (assoc "id" cal)))
552 (number (cdr (assoc "number" cal)))
553 (total (cdr (assoc "total" cal)))
554 (the-buf (current-buffer))
556 (mother mime/preview-buffer))
557 (if (not (file-exists-p root-dir))
558 (shell-command (concat "mkdir " root-dir))
560 (setq id (replace-as-filename id))
561 (setq root-dir (concat root-dir "/" id))
562 (if (not (file-exists-p root-dir))
563 (shell-command (concat "mkdir " root-dir))
565 (setq file (concat root-dir "/FULL"))
566 (if (not (file-exists-p file))
568 (re-search-forward "^$")
569 (goto-char (+ (match-end 0) 1))
570 (setq file (concat root-dir "/" number))
571 (write-region (point) (point-max) file)
572 (if (get-buffer "*MIME-temp*")
573 (kill-buffer "*MIME-temp*")
575 (switch-to-buffer "*MIME-temp*")
577 (max (string-to-int total))
581 (setq file (concat root-dir "/" (int-to-string i)))
582 (if (not (file-exists-p file))
584 (switch-to-buffer the-buf)
587 (insert-file-contents file)
588 (goto-char (point-max))
591 (delete-other-windows)
592 (write-file (concat root-dir "/FULL"))
593 (setq major-mode 'mime/show-message-mode)
594 (mime/viewer-mode mother)
595 (pop-to-buffer (current-buffer))
599 (delete-other-windows)
601 (setq major-mode 'mime/show-message-mode)
602 (mime/viewer-mode mother)
603 (pop-to-buffer (current-buffer))
607 (defun mime/get-content-decoding-alist (al)
608 (let ((r mime/content-decoding-condition) ret)
611 (if (setq ret (nth 1 (assoc-unify (car r) al)))
617 (defun mime/decode-content-region (beg end)
622 (narrow-to-region beg end)
624 (setq ctl (mime/Content-Type))
626 (setq encoding (mime/Content-Transfer-Encoding "7bit"))
629 (let ((ctype (downcase (car ctl))) method cal ret)
631 (setq cal (nconc (list (cons 'type ctype)
632 (cons 'encoding encoding)
635 (if mime/body-decoding-mode
637 (cons 'mode mime/body-decoding-mode)
640 (setq ret (mime/get-content-decoding-alist cal))
641 (setq method (cdr (assoc 'method ret)))
642 (cond ((and (symbolp method)
644 (funcall method beg end ret)
646 ((and (listp method)(stringp (car method)))
647 (mime/start-external-method-region beg end ret)
649 (t (mime/show-output-buffer
650 "No method are specified for %s\n" ctype)
655 (defun mime/show-output-buffer (&rest forms)
656 (let ((the-buf (current-buffer)))
657 (if (null (get-buffer-window mime/output-buffer-name))
658 (split-window-vertically (/ (* (window-height) 3) 4))
660 (pop-to-buffer mime/output-buffer-name)
661 (goto-char (point-max))
663 (insert (apply (function format) forms))
665 (pop-to-buffer the-buf)
670 ;;; @ MIME viewer mode
673 (defun mime/viewer-mode (&optional mother)
675 (let ((buf (get-buffer mime/output-buffer-name))
676 (the-buf (current-buffer))
680 (switch-to-buffer buf)
682 (switch-to-buffer the-buf)
684 (let ((ret (mime/parse-message))
686 (switch-to-buffer (car ret))
687 (setq major-mode 'mime/viewer-mode)
688 (setq mode-name "MIME-View")
690 (make-variable-buffer-local 'mime/viewer-original-major-mode)
691 (setq mime/viewer-original-major-mode
694 (make-variable-buffer-local
695 'mime/show-mode-old-window-configuration)
696 (setq mime/show-mode-old-window-configuration
697 (current-window-configuration))
698 (make-variable-buffer-local 'mime/mother-buffer)
699 (setq mime/mother-buffer mother)
700 'mime/show-message-mode)
702 (let ((keymap (current-local-map)))
704 (setq keymap (make-sparse-keymap))
705 (setq keymap (copy-keymap keymap))
707 (use-local-map keymap)
708 (define-key keymap "u" 'mime/up-content)
709 (define-key keymap "p" 'mime/previous-content)
710 (define-key keymap "n" 'mime/next-content)
711 (define-key keymap " " 'mime/scroll-up-content)
712 (define-key keymap "\M- " 'mime/scroll-down-content)
713 (define-key keymap "v" 'mime/play-content)
714 (define-key keymap "e" 'mime/extract-content)
715 (define-key keymap "\C-c\C-p" 'mime/print-content)
716 (define-key keymap "\C-c\C-x" 'mime/exit-view-mode)
718 (make-variable-buffer-local 'mime/preview-flat-content-list)
719 (setq mime/preview-flat-content-list (nth 1 ret))
722 (let ((ce (nth 1 (car mime/preview-flat-content-list)))
724 (goto-char (point-min))
725 (search-forward "\n\n" nil t)
726 (setq e (match-end 0))
732 (defun mime/decode-content ()
734 (let ((pc (mime/get-point-preview-content (point))))
736 (let ((the-buf (current-buffer)))
737 (switch-to-buffer (nth 2 pc))
738 (mime/decode-content-region (nth 3 pc)(nth 4 pc))
739 (if (eq (current-buffer) (nth 2 pc))
740 (switch-to-buffer the-buf)
744 (defun mime/play-content ()
746 (let ((mime/body-decoding-mode "play"))
747 (mime/decode-content)
750 (defun mime/extract-content ()
752 (let ((mime/body-decoding-mode "extract"))
753 (mime/decode-content)
756 (defun mime/print-content ()
758 (let ((mime/body-decoding-mode "print"))
759 (mime/decode-content)
762 (defun mime/up-content ()
764 (let ((pc (mime/get-point-preview-content (point)))
765 (the-buf (current-buffer))
767 (switch-to-buffer (nth 2 pc))
768 (setq cn (mime/get-point-content-number (nth 3 pc)))
770 (and (setq r (assoc major-mode mime/go-to-top-node-method-alist))
771 (switch-to-buffer the-buf)
774 (setq r (mime/get-content-region (cdr cn)))
775 (switch-to-buffer the-buf)
777 (let ((rfcl mime/preview-flat-content-list) cell)
779 (setq cell (car rfcl))
780 (if (and (= (car r)(nth 3 cell))
781 (= (nth 1 r)(nth 4 cell))
784 (goto-char (nth 0 cell))
787 (setq rfcl (cdr rfcl))
791 (defun mime/previous-content ()
793 (let* ((fcl mime/preview-flat-content-list)
795 (i (- (length fcl) 1))
799 (if (> p (car (nth i fcl)))
800 (throw 'tag (goto-char (car (nth i fcl))))
806 (defun mime/next-content ()
808 (let ((fcl mime/preview-flat-content-list)
813 (if (< p (car (car fcl)))
814 (throw 'tag (goto-char (car (car fcl))))
820 (defun mime/scroll-up-content ()
822 (let ((fcl mime/preview-flat-content-list)
824 (h (- (window-height) 1))
826 (setq np (or (catch 'tag
828 (if (< p (car (car fcl)))
829 (throw 'tag (car (car fcl)))
839 (defun mime/scroll-down-content ()
841 (let ((fcl mime/preview-flat-content-list)
843 (h (- (window-height) 1))
845 (setq pp (or (let ((i (- (length fcl) 1)))
848 (if (> p (nth 1 (nth i fcl)))
849 (throw 'tag (nth 1 (nth i fcl)))
859 (defun mime/exit-view-mode ()
861 (kill-buffer (current-buffer))
864 (fset 'mime/view-mode 'mime/viewer-mode)
866 (run-hooks 'tm-view-load-hook)