comparison lisp/url/mm.el @ 70:131b0175ea99 r20-0b30

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