comparison lisp/url/mm.el @ 0:376386a54a3c r19-14

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