comparison lisp/w3/mm.el @ 14:9ee227acff29 r19-15b90

Import from CVS: tag r19-15b90
author cvs
date Mon, 13 Aug 2007 08:48:42 +0200
parents
children 0293115a14e9 6a378aca36af
comparison
equal deleted inserted replaced
13:13c6d0aaafe5 14:9ee227acff29
1 ;;; mm.el,v --- Mailcap parsing routines, and MIME handling
2 ;; Author: wmperry
3 ;; Created: 1996/05/28 02:46:51
4 ;; Version: 1.96
5 ;; Keywords: mail, news, hypermedia
6
7 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
8 ;;; Copyright (c) 1994, 1995, 1996 by William M. Perry (wmperry@cs.indiana.edu)
9 ;;; Copyright (c) 1996 Free Software Foundation, Inc.
10 ;;;
11 ;;; This file is not part of GNU Emacs, but the same permissions apply.
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 2, or (at your option)
16 ;;; 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; see the file COPYING. If not, write to the
25 ;;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
26 ;;; Boston, MA 02111-1307, USA.
27 ;;;
28 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
29 ;;; Generalized mailcap parsing and access routines
30 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
31 ;;;
32 ;;; Data structures
33 ;;; ---------------
34 ;;; The mailcap structure is an assoc list of assoc lists.
35 ;;; 1st assoc list is keyed on the major content-type
36 ;;; 2nd assoc list is keyed on the minor content-type (which can be a regexp)
37 ;;;
38 ;;; Which looks like:
39 ;;; -----------------
40 ;;; (
41 ;;; ("application"
42 ;;; ("postscript" . <info>)
43 ;;; )
44 ;;; ("text"
45 ;;; ("plain" . <info>)
46 ;;; )
47 ;;; )
48 ;;;
49 ;;; Where <info> is another assoc list of the various information
50 ;;; related to the mailcap RFC. This is keyed on the lowercase
51 ;;; attribute name (viewer, test, etc). This looks like:
52 ;;; (("viewer" . viewerinfo)
53 ;;; ("test" . testinfo)
54 ;;; ("xxxx" . "string")
55 ;;; )
56 ;;;
57 ;;; Where viewerinfo specifies how the content-type is viewed. Can be
58 ;;; a string, in which case it is run through a shell, with
59 ;;; appropriate parameters, or a symbol, in which case the symbol is
60 ;;; funcall'd, with the buffer as an argument.
61 ;;;
62 ;;; testinfo is a list of strings, or nil. If nil, it means the
63 ;;; viewer specified is always valid. If it is a list of strings,
64 ;;; these are used to determine whether a viewer passes the 'test' or
65 ;;; not.
66 ;;;
67 ;;; The main interface to this code is:
68 ;;;
69 ;;; To set everything up:
70 ;;;
71 ;;; (mm-parse-mailcaps [path])
72 ;;;
73 ;;; Where PATH is a unix-style path specification (: separated list
74 ;;; of strings). If PATH is nil, the environment variable MAILCAPS
75 ;;; will be consulted. If there is no environment variable, then a
76 ;;; default list of paths is used.
77 ;;;
78 ;;; To retrieve the information:
79 ;;; (mm-mime-info st [nd] [request])
80 ;;;
81 ;;; Where st and nd are positions in a buffer that contain the
82 ;;; content-type header information of a mail/news/whatever message.
83 ;;; st can optionally be a string that contains the content-type
84 ;;; information.
85 ;;;
86 ;;; Third argument REQUEST specifies what information to return. If
87 ;;; it is nil or the empty string, the viewer (second field of the
88 ;;; mailcap entry) will be returned. If it is a string, then the
89 ;;; mailcap field corresponding to that string will be returned
90 ;;; (print, description, whatever). If a number, then all the
91 ;;; information for this specific viewer is returned.
92 ;;;
93 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
94 ;;; Variables, etc
95 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
96 (eval-and-compile
97 (require 'cl))
98
99 (defconst mm-version (let ((x "1.96"))
100 (if (string-match "Revision: \\([^ \t\n]+\\)" x)
101 (substring x (match-beginning 1) (match-end 1))
102 x))
103 "Version # of MM package")
104
105 (defvar mm-parse-args-syntax-table
106 (copy-syntax-table emacs-lisp-mode-syntax-table)
107 "A syntax table for parsing sgml attributes.")
108
109 (modify-syntax-entry ?' "\"" mm-parse-args-syntax-table)
110 (modify-syntax-entry ?` "\"" mm-parse-args-syntax-table)
111 (modify-syntax-entry ?{ "(" mm-parse-args-syntax-table)
112 (modify-syntax-entry ?} ")" mm-parse-args-syntax-table)
113
114 (defvar mm-mime-data
115 '(
116 ("multipart" . (
117 ("alternative". (("viewer" . mm-multipart-viewer)
118 ("type" . "multipart/alternative")))
119 ("mixed" . (("viewer" . mm-multipart-viewer)
120 ("type" . "multipart/mixed")))
121 (".*" . (("viewer" . mm-save-binary-file)
122 ("type" . "multipart/*")))
123 )
124 )
125 ("application" . (
126 ("octet-stream" . (("viewer" . mm-save-binary-file)
127 ("type" ."application/octet-stream")))
128 ("dvi" . (("viewer" . "open %s")
129 ("type" . "application/dvi")
130 ("test" . (eq (device-type) 'ns))))
131 ("dvi" . (("viewer" . "xdvi %s")
132 ("test" . (eq (device-type) 'x))
133 ("needsx11")
134 ("type" . "application/dvi")))
135 ("dvi" . (("viewer" . "dvitty %s")
136 ("test" . (not (getenv "DISPLAY")))
137 ("type" . "application/dvi")))
138 ("emacs-lisp" . (("viewer" . mm-maybe-eval)
139 ("type" . "application/emacs-lisp")))
140 ; ("x-tar" . (("viewer" . tar-mode)
141 ; ("test" . (fboundp 'tar-mode))
142 ; ("type" . "application/x-tar")))
143 ("x-tar" . (("viewer" . mm-save-binary-file)
144 ("type" . "application/x-tar")))
145 ("x-latex" . (("viewer" . tex-mode)
146 ("test" . (fboundp 'tex-mode))
147 ("type" . "application/x-latex")))
148 ("x-tex" . (("viewer" . tex-mode)
149 ("test" . (fboundp 'tex-mode))
150 ("type" . "application/x-tex")))
151 ("latex" . (("viewer" . tex-mode)
152 ("test" . (fboundp 'tex-mode))
153 ("type" . "application/latex")))
154 ("tex" . (("viewer" . tex-mode)
155 ("test" . (fboundp 'tex-mode))
156 ("type" . "application/tex")))
157 ("texinfo" . (("viewer" . texinfo-mode)
158 ("test" . (fboundp 'texinfo-mode))
159 ("type" . "application/tex")))
160 ("zip" . (("viewer" . mm-save-binary-file)
161 ("type" . "application/zip")
162 ("copiousoutput")))
163 ("pdf" . (("viewer" . "acroread %s")
164 ("type" . "application/pdf")))
165 ("postscript" . (("viewer" . "open %s")
166 ("type" . "application/postscript")
167 ("test" . (eq (device-type) 'ns))))
168 ("postscript" . (("viewer" . "ghostview %s")
169 ("type" . "application/postscript")
170 ("test" . (eq (device-type) 'x))
171 ("needsx11")))
172 ("postscript" . (("viewer" . "ps2ascii %s")
173 ("type" . "application/postscript")
174 ("test" . (not (getenv "DISPLAY")))
175 ("copiousoutput")))
176 ("x-www-pem-reply" .
177 (("viewer" . (w3-decode-pgp/pem "pem"))
178 ("test" . (fboundp 'w3-decode-pgp/pem))
179 ("type" . "application/x-www-pem-reply")
180 ))
181 ("x-www-pgp-reply" .
182 (("viewer" . (w3-decode-pgp/pem "pgp"))
183 ("test" . (fboundp 'w3-decode-pgp/pem))
184 ("type" . "application/x-www-pgp-reply")))
185 ))
186 ("audio" . (
187 ("x-mpeg" . (("viewer" . "maplay %s")
188 ("type" . "audio/x-mpeg")))
189 (".*" . (("viewer" . mm-play-sound-file)
190 ("test" . (or (featurep 'nas-sound)
191 (featurep 'native-sound)))
192 ("type" . "audio/*")))
193 (".*" . (("viewer" . "showaudio")
194 ("type" . "audio/*")))
195 ))
196 ("message" . (
197 ("rfc-*822" . (("viewer" . vm-mode)
198 ("test" . (fboundp 'vm-mode))
199 ("type" . "message/rfc-822")))
200 ("rfc-*822" . (("viewer" . w3-mode)
201 ("test" . (fboundp 'w3-mode))
202 ("type" . "message/rfc-822")))
203 ("rfc-*822" . (("viewer" . view-mode)
204 ("test" . (fboundp 'view-mode))
205 ("type" . "message/rfc-822")))
206 ("rfc-*822" . (("viewer" . fundamental-mode)
207 ("type" . "message/rfc-822")))
208 ))
209 ("image" . (
210 ("x-xwd" . (("viewer" . "xwud -in %s")
211 ("type" . "image/x-xwd")
212 ("compose" . "xwd -frame > %s")
213 ("test" . (eq (device-type) 'x))
214 ("needsx11")))
215 ("x11-dump" . (("viewer" . "xwud -in %s")
216 ("type" . "image/x-xwd")
217 ("compose" . "xwd -frame > %s")
218 ("test" . (eq (device-type) 'x))
219 ("needsx11")))
220 ("windowdump" . (("viewer" . "xwud -in %s")
221 ("type" . "image/x-xwd")
222 ("compose" . "xwd -frame > %s")
223 ("test" . (eq (device-type) 'x))
224 ("needsx11")))
225 (".*" . (("viewer" . "open %s")
226 ("type" . "image/*")
227 ("test" . (eq (device-type) 'ns))))
228 (".*" . (("viewer" . "xv -perfect %s")
229 ("type" . "image/*")
230 ("test" . (eq (device-type) 'x))
231 ("needsx11")))
232 ))
233 ("text" . (
234 ("plain" . (("viewer" . w3-mode)
235 ("test" . (fboundp 'w3-mode))
236 ("type" . "text/plain")))
237 ("plain" . (("viewer" . view-mode)
238 ("test" . (fboundp 'view-mode))
239 ("type" . "text/plain")))
240 ("plain" . (("viewer" . fundamental-mode)
241 ("type" . "text/plain")))
242 ("enriched" . (("viewer" . enriched-decode-region)
243 ("test" . (fboundp
244 'enriched-decode-region))
245 ("type" . "text/enriched")))
246 ("html" . (("viewer" . w3-prepare-buffer)
247 ("test" . (fboundp 'w3-prepare-buffer))
248 ("type" . "text/html")))
249 ))
250 ("video" . (
251 ("mpeg" . (("viewer" . "mpeg_play %s")
252 ("type" . "video/mpeg")
253 ("test" . (eq (device-type) 'x))
254 ("needsx11")))
255 ))
256 ("x-world" . (
257 ("x-vrml" . (("viewer" . "webspace -remote %s -URL %u")
258 ("type" . "x-world/x-vrml")
259 ("description"
260 "VRML document")))))
261 ("archive" . (
262 ("tar" . (("viewer" . tar-mode)
263 ("type" . "archive/tar")
264 ("test" . (fboundp 'tar-mode))))
265 ))
266 )
267 "*The mailcap structure is an assoc list of assoc lists.
268 1st assoc list is keyed on the major content-type
269 2nd assoc list is keyed on the minor content-type (which can be a regexp)
270
271 Which looks like:
272 -----------------
273 (
274 (\"application\"
275 (\"postscript\" . <info>)
276 )
277 (\"text\"
278 (\"plain\" . <info>)
279 )
280 )
281
282 Where <info> is another assoc list of the various information
283 related to the mailcap RFC. This is keyed on the lowercase
284 attribute name (viewer, test, etc). This looks like:
285 ((\"viewer\" . viewerinfo)
286 (\"test\" . testinfo)
287 (\"xxxx\" . \"string\")
288 )
289
290 Where viewerinfo specifies how the content-type is viewed. Can be
291 a string, in which case it is run through a shell, with
292 appropriate parameters, or a symbol, in which case the symbol is
293 funcall'd, with the buffer as an argument.
294
295 testinfo is a list of strings, or nil. If nil, it means the
296 viewer specified is always valid. If it is a list of strings,
297 these are used to determine whether a viewer passes the 'test' or
298 not.")
299
300 (defvar mm-content-transfer-encodings
301 '(("base64" . base64-decode)
302 ("7bit" . ignore)
303 ("8bit" . ignore)
304 ("binary" . ignore)
305 ("x-compress" . ("uncompress" "-c"))
306 ("x-gzip" . ("gzip" "-dc"))
307 ("compress" . ("uncompress" "-c"))
308 ("gzip" . ("gzip" "-dc"))
309 ("x-hqx" . ("mcvert" "-P" "-s" "-S"))
310 ("quoted-printable" . mm-decode-quoted-printable)
311 )
312 "*An assoc list of content-transfer-encodings and how to decode them.")
313
314 (defvar mm-download-directory nil
315 "*Where downloaded files should go by default.")
316
317 (defvar mm-temporary-directory "/tmp"
318 "*Where temporary files go.")
319
320
321 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
322 ;;; A few things from w3 and url, just in case this is used without them
323 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
324
325 (defun mm-generate-unique-filename (&optional fmt)
326 "Generate a unique filename in mm-temporary-directory"
327 (if (not fmt)
328 (let ((base (format "mm-tmp.%d" (user-real-uid)))
329 (fname "")
330 (x 0))
331 (setq fname (format "%s%d" base x))
332 (while (file-exists-p
333 (expand-file-name fname mm-temporary-directory))
334 (setq x (1+ x)
335 fname (concat base (int-to-string x))))
336 (expand-file-name fname mm-temporary-directory))
337 (let ((base (concat "mm" (int-to-string (user-real-uid))))
338 (fname "")
339 (x 0))
340 (setq fname (format fmt (concat base (int-to-string x))))
341 (while (file-exists-p
342 (expand-file-name fname mm-temporary-directory))
343 (setq x (1+ x)
344 fname (format fmt (concat base (int-to-string x)))))
345 (expand-file-name fname mm-temporary-directory))))
346
347 (if (and (fboundp 'copy-tree)
348 (subrp (symbol-function 'copy-tree)))
349 (fset 'mm-copy-tree 'copy-tree)
350 (defun mm-copy-tree (tree)
351 (if (consp tree)
352 (cons (mm-copy-tree (car tree))
353 (mm-copy-tree (cdr tree)))
354 (if (vectorp tree)
355 (let* ((new (copy-sequence tree))
356 (i (1- (length new))))
357 (while (>= i 0)
358 (aset new i (mm-copy-tree (aref new i)))
359 (setq i (1- i)))
360 new)
361 tree))))
362
363 (require 'mule-sysdp)
364
365 (if (not (fboundp 'w3-save-binary-file))
366 (defun mm-save-binary-file ()
367 ;; Ok, this is truly fucked. In XEmacs, if you use the mouse to select
368 ;; a URL that gets saved via this function, read-file-name will pop up a
369 ;; dialog box for file selection. For some reason which buffer we are in
370 ;; gets royally screwed (even with save-excursions and the whole nine
371 ;; yards). SO, we just keep the old buffer name around and away we go.
372 (let ((old-buff (current-buffer))
373 (file (read-file-name "Filename to save as: "
374 (or mm-download-directory "~/")
375 (file-name-nondirectory (url-view-url t))
376 nil
377 (file-name-nondirectory (url-view-url t))))
378 (require-final-newline nil))
379 (set-buffer old-buff)
380 (mule-write-region-no-coding-system (point-min) (point-max) file)
381 (kill-buffer (current-buffer))))
382 (fset 'mm-save-binary-file 'w3-save-binary-file))
383
384 (defun mm-maybe-eval ()
385 "Maybe evaluate a buffer of emacs lisp code"
386 (if (yes-or-no-p "This is emacs-lisp code, evaluate it? ")
387 (eval-buffer (current-buffer))
388 (emacs-lisp-mode)))
389
390
391 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
392 ;;; The mailcap parser
393 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
394 (defun mm-viewer-unescape (format &optional filename url)
395 (save-excursion
396 (set-buffer (get-buffer-create " *mm-parse*"))
397 (erase-buffer)
398 (insert format)
399 (goto-char (point-min))
400 (while (re-search-forward "%\\(.\\)" nil t)
401 (let ((escape (aref (match-string 1) 0)))
402 (replace-match "" t t)
403 (case escape
404 (?% (insert "%"))
405 (?s (insert (or filename "\"\"")))
406 (?u (insert (or url "\"\""))))))
407 (buffer-string)))
408
409 (defun mm-in-assoc (elt list)
410 ;; Check to see if ELT matches any of the regexps in the car elements of LIST
411 (let (rslt)
412 (while (and list (not rslt))
413 (and (car (car list))
414 (string-match (car (car list)) elt)
415 (setq rslt (car list)))
416 (setq list (cdr list)))
417 rslt))
418
419 (defun mm-replace-regexp (regexp to-string)
420 ;; Quiet replace-regexp.
421 (goto-char (point-min))
422 (while (re-search-forward regexp nil t)
423 (replace-match to-string t nil)))
424
425 (defun mm-parse-mailcaps (&optional path)
426 ;; Parse out all the mailcaps specified in a unix-style path string PATH
427 (cond
428 (path nil)
429 ((getenv "MAILCAPS") (setq path (getenv "MAILCAPS")))
430 ((memq system-type '(ms-dos ms-windows windows-nt))
431 (setq path (mapconcat 'expand-file-name '("~/mail.cap" "~/etc/mail.cap")
432 ";")))
433 (t (setq path (concat "/etc/mailcap:/usr/etc/mailcap:"
434 "/usr/local/etc/mailcap:"
435 (expand-file-name "~/.mailcap")))))
436 (let ((fnames (mm-string-to-tokens path
437 (if (memq system-type
438 '(ms-dos ms-windows windows-nt))
439 ?;
440 ?:))) fname)
441 (while fnames
442 (setq fname (car fnames))
443 (if (and (file-exists-p fname) (file-readable-p fname))
444 (mm-parse-mailcap (car fnames)))
445 (setq fnames (cdr fnames)))))
446
447 (defun mm-parse-mailcap (fname)
448 ;; Parse out the mailcap file specified by FNAME
449 (let (major ; The major mime type (image/audio/etc)
450 minor ; The minor mime type (gif, basic, etc)
451 save-pos ; Misc saved positions used in parsing
452 viewer ; How to view this mime type
453 info ; Misc info about this mime type
454 )
455 (save-excursion
456 (set-buffer (get-buffer-create " *mailcap*"))
457 (erase-buffer)
458 (insert-file-contents fname)
459 (set-syntax-table mm-parse-args-syntax-table)
460 (mm-replace-regexp "#.*" "") ; Remove all comments
461 (mm-replace-regexp "\n+" "\n") ; And blank lines
462 (mm-replace-regexp "\\\\[ \t\n]+" " ") ; And collapse spaces
463 (mm-replace-regexp (concat (regexp-quote "\\") "[ \t]*\n") "")
464 (goto-char (point-max))
465 (skip-chars-backward " \t\n")
466 (delete-region (point) (point-max))
467 (goto-char (point-min))
468 (while (not (eobp))
469 (skip-chars-forward " \t\n")
470 (setq save-pos (point)
471 info nil)
472 (skip-chars-forward "^/;")
473 (downcase-region save-pos (point))
474 (setq major (buffer-substring save-pos (point)))
475 (skip-chars-forward "/ \t\n")
476 (setq save-pos (point))
477 (skip-chars-forward "^;")
478 (downcase-region save-pos (point))
479 (setq minor
480 (cond
481 ((= ?* (or (char-after save-pos) 0)) ".*")
482 ((= (point) save-pos) ".*")
483 (t (buffer-substring save-pos (point)))))
484 (skip-chars-forward "; \t\n")
485 ;;; Got the major/minor chunks, now for the viewers/etc
486 ;;; The first item _must_ be a viewer, according to the
487 ;;; RFC for mailcap files (#1343)
488 (skip-chars-forward "; \t\n")
489 (setq save-pos (point))
490 (skip-chars-forward "^;\n")
491 (if (= (or (char-after save-pos) 0) ?')
492 (setq viewer (progn
493 (narrow-to-region (1+ save-pos) (point))
494 (goto-char (point-min))
495 (prog1
496 (read (current-buffer))
497 (goto-char (point-max))
498 (widen))))
499 (setq viewer (buffer-substring save-pos (point))))
500 (setq save-pos (point))
501 (end-of-line)
502 (setq info (nconc (list (cons "viewer" viewer)
503 (cons "type" (concat major "/"
504 (if (string= minor ".*")
505 "*" minor))))
506 (mm-parse-mailcap-extras save-pos (point))))
507 (mm-mailcap-entry-passes-test info)
508 (mm-add-mailcap-entry major minor info)))))
509
510 (defun mm-parse-mailcap-extras (st nd)
511 ;; Grab all the extra stuff from a mailcap entry
512 (let (
513 name ; From name=
514 value ; its value
515 results ; Assoc list of results
516 name-pos ; Start of XXXX= position
517 val-pos ; Start of value position
518 done ; Found end of \'d ;s?
519 )
520 (save-restriction
521 (narrow-to-region st nd)
522 (goto-char (point-min))
523 (skip-chars-forward " \n\t;")
524 (while (not (eobp))
525 (setq done nil)
526 (skip-chars-forward " \";\n\t")
527 (setq name-pos (point))
528 (skip-chars-forward "^ \n\t=")
529 (downcase-region name-pos (point))
530 (setq name (buffer-substring name-pos (point)))
531 (skip-chars-forward " \t\n")
532 (if (/= (or (char-after (point)) 0) ?=) ; There is no value
533 (setq value nil)
534 (skip-chars-forward " \t\n=")
535 (setq val-pos (point))
536 (if (memq (char-after val-pos) '(?\" ?'))
537 (progn
538 (setq val-pos (1+ val-pos))
539 (condition-case nil
540 (progn
541 (forward-sexp 1)
542 (backward-char 1))
543 (error (goto-char (point-max)))))
544 (while (not done)
545 (skip-chars-forward "^;")
546 (if (= (or (char-after (1- (point))) 0) ?\\ )
547 (progn
548 (subst-char-in-region (1- (point)) (point) ?\\ ? )
549 (skip-chars-forward ";"))
550 (setq done t))))
551 (setq value (buffer-substring val-pos (point))))
552 (setq results (cons (cons name value) results)))
553 results)))
554
555 (defun mm-string-to-tokens (str &optional delim)
556 "Return a list of words from the string STR"
557 (setq delim (or delim ? ))
558 (let (results y)
559 (mapcar
560 (function
561 (lambda (x)
562 (cond
563 ((and (= x delim) y) (setq results (cons y results) y nil))
564 ((/= x delim) (setq y (concat y (char-to-string x))))
565 (t nil)))) str)
566 (nreverse (cons y results))))
567
568 (defun mm-mailcap-entry-passes-test (info)
569 ;; Return t iff a mailcap entry passes its test clause or no test
570 ;; clause is present.
571 (let (status ; Call-process-regions return value
572 (test (assoc "test" info)); The test clause
573 )
574 (setq status (and test (mm-string-to-tokens (cdr test))))
575 (if (and (assoc "needsx11" info) (not (getenv "DISPLAY")))
576 (setq status nil)
577 (cond
578 ((and (equal (nth 0 status) "test")
579 (equal (nth 1 status) "-n")
580 (or (equal (nth 2 status) "$DISPLAY")
581 (equal (nth 2 status) "\"$DISPLAY\"")))
582 (setq status (if (getenv "DISPLAY") t nil)))
583 ((and (equal (nth 0 status) "test")
584 (equal (nth 1 status) "-z")
585 (or (equal (nth 2 status) "$DISPLAY")
586 (equal (nth 2 status) "\"$DISPLAY\"")))
587 (setq status (if (getenv "DISPLAY") nil t)))
588 (test nil)
589 (t nil)))
590 (and test (listp test) (setcdr test status))))
591
592 (defun mm-parse-args (st &optional nd nodowncase)
593 ;; Return an assoc list of attribute/value pairs from an RFC822-type string
594 (let (
595 name ; From name=
596 value ; its value
597 results ; Assoc list of results
598 name-pos ; Start of XXXX= position
599 val-pos ; Start of value position
600 )
601 (save-excursion
602 (if (stringp st)
603 (progn
604 (set-buffer (get-buffer-create " *mm-temp*"))
605 (set-syntax-table mm-parse-args-syntax-table)
606 (erase-buffer)
607 (insert st)
608 (setq st (point-min)
609 nd (point-max)))
610 (set-syntax-table mm-parse-args-syntax-table))
611 (save-restriction
612 (narrow-to-region st nd)
613 (goto-char (point-min))
614 (while (not (eobp))
615 (skip-chars-forward "; \n\t")
616 (setq name-pos (point))
617 (skip-chars-forward "^ \n\t=;")
618 (if (not nodowncase)
619 (downcase-region name-pos (point)))
620 (setq name (buffer-substring name-pos (point)))
621 (skip-chars-forward " \t\n")
622 (if (/= (or (char-after (point)) 0) ?=) ; There is no value
623 (setq value nil)
624 (skip-chars-forward " \t\n=")
625 (setq val-pos (point)
626 value
627 (cond
628 ((or (= (or (char-after val-pos) 0) ?\")
629 (= (or (char-after val-pos) 0) ?'))
630 (buffer-substring (1+ val-pos)
631 (condition-case ()
632 (prog2
633 (forward-sexp 1)
634 (1- (point))
635 (skip-chars-forward "\""))
636 (error
637 (skip-chars-forward "^ \t\n")
638 (point)))))
639 (t
640 (buffer-substring val-pos
641 (progn
642 (skip-chars-forward "^;")
643 (skip-chars-backward " \t")
644 (point)))))))
645 (setq results (cons (cons name value) results))
646 (skip-chars-forward "; \n\t"))
647 results))))
648
649 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
650 ;;; The action routines.
651 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
652 (defun mm-possible-viewers (major minor)
653 ;; Return a list of possible viewers from MAJOR for minor type MINOR
654 (let ((exact '())
655 (wildcard '()))
656 (while major
657 (cond
658 ((equal (car (car major)) minor)
659 (setq exact (cons (cdr (car major)) exact)))
660 ((string-match (car (car major)) minor)
661 (setq wildcard (cons (cdr (car major)) wildcard))))
662 (setq major (cdr major)))
663 (nconc (nreverse exact) (nreverse wildcard))))
664
665 (defun mm-unescape-mime-test (test type-info)
666 (let ((buff (get-buffer-create " *unescape*"))
667 save-pos save-chr subst)
668 (cond
669 ((symbolp test) test)
670 ((and (listp test) (symbolp (car test))) test)
671 ((or (stringp test)
672 (and (listp test) (stringp (car test))
673 (setq test (mapconcat 'identity test " "))))
674 (save-excursion
675 (set-buffer buff)
676 (erase-buffer)
677 (insert test)
678 (goto-char (point-min))
679 (while (not (eobp))
680 (skip-chars-forward "^%")
681 (if (/= (- (point)
682 (progn (skip-chars-backward "\\\\")
683 (point)))
684 0) ; It is an escaped %
685 (progn
686 (delete-char 1)
687 (skip-chars-forward "%."))
688 (setq save-pos (point))
689 (skip-chars-forward "%")
690 (setq save-chr (char-after (point)))
691 (cond
692 ((null save-chr) nil)
693 ((= save-chr ?t)
694 (delete-region save-pos (progn (forward-char 1) (point)))
695 (insert (or (cdr (assoc "type" type-info)) "\"\"")))
696 ((= save-chr ?M)
697 (delete-region save-pos (progn (forward-char 1) (point)))
698 (insert "\"\""))
699 ((= save-chr ?n)
700 (delete-region save-pos (progn (forward-char 1) (point)))
701 (insert "\"\""))
702 ((= save-chr ?F)
703 (delete-region save-pos (progn (forward-char 1) (point)))
704 (insert "\"\""))
705 ((= save-chr ?{)
706 (forward-char 1)
707 (skip-chars-forward "^}")
708 (downcase-region (+ 2 save-pos) (point))
709 (setq subst (buffer-substring (+ 2 save-pos) (point)))
710 (delete-region save-pos (1+ (point)))
711 (insert (or (cdr (assoc subst type-info)) "\"\"")))
712 (t nil))))
713 (buffer-string)))
714 (t (error "Bad value to mm-unescape-mime-test. %s" test)))))
715
716 (defun mm-viewer-passes-test (viewer-info type-info)
717 ;; Return non-nil iff the viewer specified by VIEWER-INFO passes its
718 ;; test clause (if any).
719 (let* ((test-info (assoc "test" viewer-info))
720 (test (cdr test-info))
721 (viewer (cdr (assoc "viewer" viewer-info)))
722 status
723 parsed-test
724 )
725 (cond
726 ((not test-info) t) ; No test clause
727 ((not test) nil) ; Already failed test
728 ((eq test t) t) ; Already passed test
729 ((and (symbolp test) ; Lisp function as test
730 (fboundp test))
731 (funcall test type-info))
732 ((and (symbolp test) ; Lisp variable as test
733 (boundp test))
734 (symbol-value test))
735 ((and (listp test) ; List to be eval'd
736 (symbolp (car test)))
737 (eval test))
738 (t
739 (setq test (mm-unescape-mime-test test type-info)
740 test (list "/bin/sh" nil nil nil "-c" test)
741 status (apply 'call-process test))
742 (= 0 status)))))
743
744 (defun mm-add-mailcap-entry (major minor info)
745 (let ((old-major (assoc major mm-mime-data)))
746 (if (null old-major) ; New major area
747 (setq mm-mime-data
748 (cons (cons major (list (cons minor info)))
749 mm-mime-data))
750 (let ((cur-minor (assoc minor old-major)))
751 (cond
752 ((or (null cur-minor) ; New minor area, or
753 (assoc "test" info)) ; Has a test, insert at beginning
754 (setcdr old-major (cons (cons minor info) (cdr old-major))))
755 ((and (not (assoc "test" info)); No test info, replace completely
756 (not (assoc "test" cur-minor)))
757 (setcdr cur-minor info))
758 (t
759 (setcdr old-major (cons (cons minor info) (cdr old-major)))))))))
760
761
762 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
763 ;;; The main whabbo
764 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
765 (defun mm-viewer-lessp (x y)
766 ;; Return t iff viewer X is more desirable than viewer Y
767 (let ((x-wild (string-match "[*?]" (or (cdr-safe (assoc "type" x)) "")))
768 (y-wild (string-match "[*?]" (or (cdr-safe (assoc "type" y)) "")))
769 (x-lisp (not (stringp (or (cdr-safe (assoc "viewer" x)) ""))))
770 (y-lisp (not (stringp (or (cdr-safe (assoc "viewer" y)) "")))))
771 (cond
772 ((and x-lisp (not y-lisp))
773 t)
774 ((and (not y-lisp) x-wild (not y-wild))
775 t)
776 ((and (not x-wild) y-wild)
777 t)
778 (t nil))))
779
780 (defun mm-mime-info (st &optional nd request)
781 "Get the mime viewer command for HEADERLINE, return nil if none found.
782 Expects a complete content-type header line as its argument. This can
783 be simple like text/html, or complex like text/plain; charset=blah; foo=bar
784
785 Third argument REQUEST specifies what information to return. If it is
786 nil or the empty string, the viewer (second field of the mailcap
787 entry) will be returned. If it is a string, then the mailcap field
788 corresponding to that string will be returned (print, description,
789 whatever). If a number, then all the information for this specific
790 viewer is returned."
791 (let (
792 major ; Major encoding (text, etc)
793 minor ; Minor encoding (html, etc)
794 info ; Other info
795 save-pos ; Misc. position during parse
796 major-info ; (assoc major mm-mime-data)
797 minor-info ; (assoc minor major-info)
798 test ; current test proc.
799 viewers ; Possible viewers
800 passed ; Viewers that passed the test
801 viewer ; The one and only viewer
802 )
803 (save-excursion
804 (cond
805 ((null st)
806 (set-buffer (get-buffer-create " *mimeparse*"))
807 (erase-buffer)
808 (insert "text/plain")
809 (setq st (point-min)))
810 ((stringp st)
811 (set-buffer (get-buffer-create " *mimeparse*"))
812 (erase-buffer)
813 (insert st)
814 (setq st (point-min)))
815 ((null nd)
816 (narrow-to-region st (progn (goto-char st) (end-of-line) (point))))
817 (t (narrow-to-region st nd)))
818 (goto-char st)
819 (skip-chars-forward ": \t\n")
820 (buffer-enable-undo)
821 (setq viewer
822 (catch 'mm-exit
823 (setq save-pos (point))
824 (skip-chars-forward "^/")
825 (downcase-region save-pos (point))
826 (setq major (buffer-substring save-pos (point)))
827 (if (not (setq major-info (cdr (assoc major mm-mime-data))))
828 (throw 'mm-exit nil))
829 (skip-chars-forward "/ \t\n")
830 (setq save-pos (point))
831 (skip-chars-forward "^ \t\n;")
832 (downcase-region save-pos (point))
833 (setq minor (buffer-substring save-pos (point)))
834 (if (not
835 (setq viewers (mm-possible-viewers major-info minor)))
836 (throw 'mm-exit nil))
837 (skip-chars-forward "; \t")
838 (if (eolp)
839 nil ; No qualifiers
840 (setq save-pos (point))
841 (end-of-line)
842 (setq info (mm-parse-args save-pos (point)))
843 )
844 (while viewers
845 (if (mm-viewer-passes-test (car viewers) info)
846 (setq passed (cons (car viewers) passed)))
847 (setq viewers (cdr viewers)))
848 (setq passed (sort (nreverse passed) 'mm-viewer-lessp))
849 (car passed)))
850 (if (and (stringp (cdr (assoc "viewer" viewer)))
851 passed)
852 (setq viewer (car passed)))
853 (widen)
854 (cond
855 ((and (null viewer) (not (equal major "default")))
856 (mm-mime-info "default" nil request))
857 ((or (null request) (equal request ""))
858 (mm-unescape-mime-test (cdr (assoc "viewer" viewer)) info))
859 ((stringp request)
860 (if (or (string= request "test") (string= request "viewer"))
861 (mm-unescape-mime-test (cdr-safe (assoc request viewer)) info)))
862 (t
863 ;; MUST make a copy *sigh*, else we modify mm-mime-data
864 (setq viewer (mm-copy-tree viewer))
865 (let ((view (assoc "viewer" viewer))
866 (test (assoc "test" viewer)))
867 (if view (setcdr view (mm-unescape-mime-test (cdr view) info)))
868 (if test (setcdr test (mm-unescape-mime-test (cdr test) info))))
869 viewer)))))
870
871
872 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
873 ;;; Experimental MIME-types parsing
874 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
875 (defvar mm-mime-extensions
876 '(
877 ("" . "text/plain")
878 (".abs" . "audio/x-mpeg")
879 (".aif" . "audio/aiff")
880 (".aifc" . "audio/aiff")
881 (".aiff" . "audio/aiff")
882 (".ano" . "application/x-annotator")
883 (".au" . "audio/ulaw")
884 (".avi" . "video/x-msvideo")
885 (".bcpio" . "application/x-bcpio")
886 (".bin" . "application/octet-stream")
887 (".cdf" . "application/x-netcdr")
888 (".cpio" . "application/x-cpio")
889 (".csh" . "application/x-csh")
890 (".dvi" . "application/x-dvi")
891 (".el" . "application/emacs-lisp")
892 (".eps" . "application/postscript")
893 (".etx" . "text/x-setext")
894 (".exe" . "application/octet-stream")
895 (".fax" . "image/x-fax")
896 (".gif" . "image/gif")
897 (".hdf" . "application/x-hdf")
898 (".hqx" . "application/mac-binhex40")
899 (".htm" . "text/html")
900 (".html" . "text/html")
901 (".icon" . "image/x-icon")
902 (".ief" . "image/ief")
903 (".jpg" . "image/jpeg")
904 (".macp" . "image/x-macpaint")
905 (".man" . "application/x-troff-man")
906 (".me" . "application/x-troff-me")
907 (".mif" . "application/mif")
908 (".mov" . "video/quicktime")
909 (".movie" . "video/x-sgi-movie")
910 (".mp2" . "audio/x-mpeg")
911 (".mp2a" . "audio/x-mpeg2")
912 (".mpa" . "audio/x-mpeg")
913 (".mpa2" . "audio/x-mpeg2")
914 (".mpe" . "video/mpeg")
915 (".mpeg" . "video/mpeg")
916 (".mpega" . "audio/x-mpeg")
917 (".mpegv" . "video/mpeg")
918 (".mpg" . "video/mpeg")
919 (".mpv" . "video/mpeg")
920 (".ms" . "application/x-troff-ms")
921 (".nc" . "application/x-netcdf")
922 (".nc" . "application/x-netcdf")
923 (".oda" . "application/oda")
924 (".pbm" . "image/x-portable-bitmap")
925 (".pdf" . "application/pdf")
926 (".pgm" . "image/portable-graymap")
927 (".pict" . "image/pict")
928 (".pnm" . "image/x-portable-anymap")
929 (".ppm" . "image/portable-pixmap")
930 (".ps" . "application/postscript")
931 (".qt" . "video/quicktime")
932 (".ras" . "image/x-raster")
933 (".rgb" . "image/x-rgb")
934 (".rtf" . "application/rtf")
935 (".rtx" . "text/richtext")
936 (".sh" . "application/x-sh")
937 (".sit" . "application/x-stuffit")
938 (".snd" . "audio/basic")
939 (".src" . "application/x-wais-source")
940 (".tar" . "archive/tar")
941 (".tcl" . "application/x-tcl")
942 (".tcl" . "application/x-tcl")
943 (".tex" . "application/x-tex")
944 (".texi" . "application/texinfo")
945 (".tga" . "image/x-targa")
946 (".tif" . "image/tiff")
947 (".tiff" . "image/tiff")
948 (".tr" . "application/x-troff")
949 (".troff" . "application/x-troff")
950 (".tsv" . "text/tab-separated-values")
951 (".txt" . "text/plain")
952 (".vbs" . "video/mpeg")
953 (".vox" . "audio/basic")
954 (".vrml" . "x-world/x-vrml")
955 (".wav" . "audio/x-wav")
956 (".wrl" . "x-world/x-vrml")
957 (".xbm" . "image/xbm")
958 (".xpm" . "image/x-pixmap")
959 (".xwd" . "image/windowdump")
960 (".zip" . "application/zip")
961 (".ai" . "application/postscript")
962 (".jpe" . "image/jpeg")
963 (".jpeg" . "image/jpeg")
964 )
965 "*An assoc list of file extensions and the MIME content-types they
966 correspond to.")
967
968 (defun mm-parse-mimetypes (&optional path)
969 ;; Parse out all the mimetypes specified in a unix-style path string PATH
970 (cond
971 (path nil)
972 ((getenv "MIMETYPES") (setq path (getenv "MIMETYPES")))
973 ((memq system-type '(ms-dos ms-windows windows-nt))
974 (setq path (mapconcat 'expand-file-name
975 '("~/mime.typ" "~/etc/mime.typ") ";")))
976 (t (setq path (concat (expand-file-name "~/.mime-types") ":"
977 "/etc/mime-types:/usr/etc/mime-types:"
978 "/usr/local/etc/mime-types:"
979 "/usr/local/www/conf/mime-types"))))
980 (let ((fnames (mm-string-to-tokens path
981 (if (memq system-type
982 '(ms-dos ms-windows windows-nt))
983 ?;
984 ?:))) fname)
985 (while fnames
986 (setq fname (car fnames))
987 (if (and (file-exists-p fname) (file-readable-p fname))
988 (mm-parse-mimetype-file (car fnames)))
989 (setq fnames (cdr fnames)))))
990
991 (defun mm-parse-mimetype-file (fname)
992 ;; Parse out a mime-types file
993 (let (type ; The MIME type for this line
994 extns ; The extensions for this line
995 save-pos ; Misc. saved buffer positions
996 )
997 (save-excursion
998 (set-buffer (get-buffer-create " *mime-types*"))
999 (erase-buffer)
1000 (insert-file-contents fname)
1001 (mm-replace-regexp "#.*" "")
1002 (mm-replace-regexp "\n+" "\n")
1003 (mm-replace-regexp "[ \t]+$" "")
1004 (goto-char (point-max))
1005 (skip-chars-backward " \t\n")
1006 (delete-region (point) (point-max))
1007 (goto-char (point-min))
1008 (while (not (eobp))
1009 (skip-chars-forward " \t\n")
1010 (setq save-pos (point))
1011 (skip-chars-forward "^ \t")
1012 (downcase-region save-pos (point))
1013 (setq type (buffer-substring save-pos (point)))
1014 (while (not (eolp))
1015 (skip-chars-forward " \t")
1016 (setq save-pos (point))
1017 (skip-chars-forward "^ \t\n")
1018 (setq extns (cons (buffer-substring save-pos (point)) extns)))
1019 (while extns
1020 (setq mm-mime-extensions
1021 (cons
1022 (cons (if (= (string-to-char (car extns)) ?.)
1023 (car extns)
1024 (concat "." (car extns))) type) mm-mime-extensions)
1025 extns (cdr extns)))))))
1026
1027 (defun mm-extension-to-mime (extn)
1028 "Return the MIME content type of the file extensions EXTN"
1029 (if (and (stringp extn)
1030 (not (= (string-to-char extn) ?.)))
1031 (setq extn (concat "." extn)))
1032 (cdr (assoc (downcase extn) mm-mime-extensions)))
1033
1034
1035 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1036 ;;; Editing/Composition of body parts
1037 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1038 (defun mm-compose-type (type)
1039 ;; Compose a body section of MIME-type TYPE.
1040 (let* ((info (mm-mime-info type nil 5))
1041 (fnam (mm-generate-unique-filename))
1042 (comp (or (cdr (assoc "compose" info))))
1043 (ctyp (cdr (assoc "composetyped" info)))
1044 (buff (get-buffer-create " *mimecompose*"))
1045 (typeit (not ctyp))
1046 (retval "")
1047 (usef nil))
1048 (setq comp (mm-unescape-mime-test (or comp ctyp) info))
1049 (while (string-match "\\([^\\\\]\\)%s" comp)
1050 (setq comp (concat (substring comp 0 (match-end 1)) fnam
1051 (substring comp (match-end 0) nil))
1052 usef t))
1053 (call-process (or shell-file-name
1054 (getenv "ESHELL") (getenv "SHELL") "/bin/sh")
1055 nil (if usef nil buff) nil "-c" comp)
1056 (setq retval
1057 (concat
1058 (if typeit (concat "Content-type: " type "\r\n\r\n") "")
1059 (if usef
1060 (save-excursion
1061 (set-buffer buff)
1062 (erase-buffer)
1063 (insert-file-contents fnam)
1064 (buffer-string))
1065 (save-excursion
1066 (set-buffer buff)
1067 (buffer-string)))
1068 "\r\n"))
1069 retval))
1070
1071 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1072 ;;; Misc.
1073 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1074 (defun mm-type-to-file (type)
1075 "Return the file extension for content-type TYPE"
1076 (rassoc type mm-mime-extensions))
1077
1078
1079 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1080 ;;; Miscellaneous MIME viewers written in elisp
1081 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1082 (defun mm-play-sound-file (&optional buff)
1083 "Play a sound file in buffer BUFF (defaults to current buffer)"
1084 (setq buff (or buff (current-buffer)))
1085 (let ((fname (mm-generate-unique-filename "%s.au"))
1086 (synchronous-sounds t)) ; Play synchronously
1087 (mm-write-region-no-coding-system (point-min) (point-max) fname)
1088 (kill-buffer (current-buffer))
1089 (play-sound-file fname)
1090 (condition-case ()
1091 (delete-file fname)
1092 (error nil))))
1093
1094 (defun mm-parse-mime-headers (&optional no-delete)
1095 "Return a list of the MIME headers at the top of this buffer. If
1096 optional argument NO-DELETE is non-nil, don't delete the headers."
1097 (let* ((st (point-min))
1098 (nd (progn
1099 (goto-char (point-min))
1100 (skip-chars-forward " \t\n")
1101 (if (re-search-forward "^\r*$" nil t)
1102 (1+ (point))
1103 (point-max))))
1104 save-pos
1105 status
1106 hname
1107 hvalu
1108 result
1109 )
1110 (narrow-to-region st nd)
1111 (goto-char (point-min))
1112 (while (not (eobp))
1113 (skip-chars-forward " \t\n\r")
1114 (setq save-pos (point))
1115 (skip-chars-forward "^:\n\r")
1116 (downcase-region save-pos (point))
1117 (setq hname (buffer-substring save-pos (point)))
1118 (skip-chars-forward ": \t ")
1119 (setq save-pos (point))
1120 (skip-chars-forward "^\n\r")
1121 (setq hvalu (buffer-substring save-pos (point))
1122 result (cons (cons hname hvalu) result)))
1123 (or no-delete (delete-region st nd))
1124 result))
1125
1126 (defun mm-find-available-multiparts (separator &optional buf)
1127 "Return a list of mime-headers for the various body parts of a
1128 multipart message in buffer BUF with separator SEPARATOR.
1129 The different multipart specs are put in `mm-temporary-directory'."
1130 (let ((sep (concat "^--" separator "\r*$"))
1131 headers
1132 fname
1133 results)
1134 (save-excursion
1135 (and buf (set-buffer buf))
1136 (goto-char (point-min))
1137 (while (re-search-forward sep nil t)
1138 (let ((st (set-marker (make-marker)
1139 (progn
1140 (forward-line 1)
1141 (beginning-of-line)
1142 (point))))
1143 (nd (set-marker (make-marker)
1144 (if (re-search-forward sep nil t)
1145 (1- (match-beginning 0))
1146 (point-max)))))
1147 (narrow-to-region st nd)
1148 (goto-char st)
1149 (if (looking-at "^\r*$")
1150 (insert "Content-type: text/plain\n"
1151 "Content-length: " (int-to-string (- nd st)) "\n"))
1152 (setq headers (mm-parse-mime-headers)
1153 fname (mm-generate-unique-filename))
1154 (let ((x (or (cdr (assoc "content-type" headers)) "text/plain")))
1155 (if (string-match "name=\"*\\([^ \"]+\\)\"*" x)
1156 (setq fname (expand-file-name
1157 (substring x (match-beginning 1)
1158 (match-end 1))
1159 mm-temporary-directory))))
1160 (widen)
1161 (if (assoc "content-transfer-encoding" headers)
1162 (let ((coding (cdr
1163 (assoc "content-transfer-encoding" headers)))
1164 (cmd nil))
1165 (setq coding (and coding (downcase coding))
1166 cmd (or (cdr (assoc coding
1167 mm-content-transfer-encodings))
1168 (read-string
1169 (concat "How shall I decode " coding "? ")
1170 "cat")))
1171 (if (string= cmd "") (setq cmd "cat"))
1172 (if (stringp cmd)
1173 (shell-command-on-region st nd cmd t)
1174 (funcall cmd st nd))
1175 (set-marker nd (point))))
1176 (write-region st nd fname nil 5)
1177 (delete-region st nd)
1178 (setq results (cons
1179 (cons
1180 (cons "mm-filename" fname) headers) results)))))
1181 results))
1182
1183 (defun mm-format-multipart-as-html (&optional buf type)
1184 (if buf (set-buffer buf))
1185 (let* ((boundary (if (string-match
1186 "boundary[ \t]*=[ \t\"]*\\([^ \"\t\n]+\\)"
1187 type)
1188 (regexp-quote
1189 (substring type (match-beginning 1) (match-end 1)))))
1190 (parts (mm-find-available-multiparts boundary)))
1191 (erase-buffer)
1192 (insert "<html>\n"
1193 " <head>\n"
1194 " <title>Multipart Message</title>\n"
1195 " </head>\n"
1196 " <body>\n"
1197 " <h1> Multipart message encountered </h1>\n"
1198 " <p> I have encountered a multipart MIME message.\n"
1199 " The following parts have been detected. Please\n"
1200 " select which one you want to view.\n"
1201 " </p>\n"
1202 " <ul>\n"
1203 (mapconcat
1204 (function (lambda (x)
1205 (concat " <li> <a href=\"file:"
1206 (cdr (assoc "mm-filename" x))
1207 "\">"
1208 (or (cdr (assoc "content-description" x)) "")
1209 "--"
1210 (or (cdr (assoc "content-type" x))
1211 "unknown type")
1212 "</a> </li>")))
1213 parts "\n")
1214 " </ul>\n"
1215 " </body>\n"
1216 "</html>\n"
1217 "<!-- Automatically generated by MM v" mm-version "-->\n")))
1218
1219 (defun mm-multipart-viewer ()
1220 (mm-format-multipart-as-html
1221 (current-buffer)
1222 (cdr (assoc "content-type" url-current-mime-headers)))
1223 (let ((w3-working-buffer (current-buffer)))
1224 (w3-prepare-buffer)))
1225
1226 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1227 ;;; Transfer encodings we can decrypt automatically
1228 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1229 (defun mm-decode-quoted-printable (&optional st nd)
1230 (interactive)
1231 (setq st (or st (point-min))
1232 nd (or nd (point-max)))
1233 (save-restriction
1234 (narrow-to-region st nd)
1235 (save-excursion
1236 (let ((buffer-read-only nil))
1237 (goto-char (point-min))
1238 (while (re-search-forward "=[0-9A-F][0-9A-F]" nil t)
1239 (replace-match
1240 (char-to-string
1241 (+
1242 (* 16 (mm-hex-char-to-integer
1243 (char-after (1+ (match-beginning 0)))))
1244 (mm-hex-char-to-integer
1245 (char-after (1- (match-end 0))))))))))))
1246
1247 ;; Taken from hexl.el.
1248 (defun mm-hex-char-to-integer (character)
1249 "Take a char and return its value as if it was a hex digit."
1250 (if (and (>= character ?0) (<= character ?9))
1251 (- character ?0)
1252 (let ((ch (logior character 32)))
1253 (if (and (>= ch ?a) (<= ch ?f))
1254 (- ch (- ?a 10))
1255 (error (format "Invalid hex digit `%c'." ch))))))
1256
1257
1258 (require 'base64)
1259 (provide 'mm)