git.fiddlerwoaroof.com
mime-api.cl
c14b148a
 #+(version= 8 1)
ade4ba4a
 (sys:defpatch "mime" 4
873db4fd
   "v1: changes to internal/undocumented portions of module;
ce1d2c5c
 v2: better parse-mime-structure behavior in the face of malformatted headers;
ade4ba4a
 v3: Improved performance when converting charset to external-format;
3f4b1cb5
 v4: decode-header-text: handle underscore & remove soft newlines."
c14b148a
   :type :system
   :post-loadable t)
 
aca0a611
 #+(version= 8 0)
c781d4b2
 (sys:defpatch "mime" 3
ad3f5e60
   "v0: New module.  See documentation.;
a6202327
 v1: Improve default transfer encoding determination;
c781d4b2
 v2: make-mime-part: Default external-format is :utf8;
 v3: add mime structure parsing support."
aca0a611
   :type :system
   :post-loadable t)
 
eb76a3f2
 ;; -*- mode: common-lisp; package: net.post-office -*-
 ;;
 ;; imap.cl
 ;; imap and pop interface
 ;;
48cdc2ae
 ;; See the file LICENSE for the full license governing this code.
eb76a3f2
 ;;
f6cd6a72
 
 (defpackage :net.post-office
   (:use #:lisp #:excl)
   (:export
    ;; functions/methods
    #:make-mime-part
    #:mime-part-writer
    #:mime-part-p
    #:mime-part-constructed-p
c781d4b2
    #:map-over-parts
36132e24
    #:decode-header-text
f6cd6a72
    
    ;; macros
    #:mime-get-header
    #:with-mime-part-constructed-stream
b45f6462
 
    ;; classes
    #:mime-part-constructed
f6cd6a72
    
    ;; slot accessors
    #:mime-part-type
    #:mime-part-subtype
    #:mime-part-parameters
    #:mime-part-id
    #:mime-part-description
    #:mime-part-encoding
    #:mime-part-headers
    #:mime-part-parts
    #:mime-part-boundary))
 
1a86640a
 (provide :mime)
 
f6cd6a72
 (in-package :net.post-office)
 
 (eval-when (compile load eval)
   (require :osi)
   (require :regexp2))
 
 (defclass mime-part ()
   (
    (type :accessor mime-part-type :initform nil)
    (subtype :accessor mime-part-subtype :initform nil)
    (parameters :accessor mime-part-parameters :initform nil) ;; alist
    (id :accessor mime-part-id :initform nil)
    (description :accessor mime-part-description :initform nil)
    (encoding :accessor mime-part-encoding :initform nil)
    (headers ;; parsed headers alist
     :accessor mime-part-headers :initform nil)
    (parts ;; list of subparts (for multipart types)
     :accessor mime-part-parts :initform nil)
    (boundary :accessor mime-part-boundary :initform nil)))
 
 (defclass mime-part-constructed (mime-part)
   (
    (source-type :accessor source-type)
    (source :accessor source)
    (disposition :accessor disposition :initform nil)
    (disposition-name :accessor disposition-name :initform nil)))
 
 (defmacro mime-get-header (header part)
   `(cdr (assoc ,header (mime-part-headers ,part) :test #'equalp)))
 
 (defun message-rfc822-p (type subtype)
   (and (equalp type "message") (equalp subtype "rfc822")))
 
 (defun multipart-p (part)
   (equalp (mime-part-type part) "multipart"))
 
 (defun multipart-mixed-p (part)
   (and (equalp (mime-part-type part) "multipart")
        (equalp (mime-part-subtype part) "mixed")))
 
 (defun mime-part-p (thing)
   (typep thing 'mime-part))
 
 (defun mime-part-constructed-p (thing)
   (typep thing 'mime-part-constructed))
 
 (defun generate-boundary ()
   (declare (optimize (speed 3)))
   (let ((hex "01234567890abcdef"))
     (with-output-to-string (s)
       (write-string "----------_" s)
       (dotimes (n 32)
 	(write-char (schar hex (random 16)) s)))))
 
 (defun make-mime-part (&key content-type encoding headers 
 			    (attachmentp nil attachmentp-supplied)
 			    name text (start 0) end file
a6202327
 			    subparts (external-format :utf8)
f6cd6a72
 			    parameters charset id description)
   (let ((part (make-instance 'mime-part-constructed))
a6202327
 	type subtype multipart textp filepath orig-text)
f6cd6a72
     
     (if* (and text file)
        then (error "Only one of :text or :file may be specified"))
     
     (if* (and text (null end))
        then (setf end (length text)))
 
     (when file
       (if* (streamp file)
 	 then (setf filepath (ignore-errors (namestring file)))
 	 else (setf filepath file)))
a6202327
 
     ;; Select default content-type
f6cd6a72
     (when (null content-type)
       (if* filepath
 	 then (setf content-type (lookup-mime-type filepath)))
       
       (when (null content-type)
 	(setf content-type
 	  (if* subparts 
 	     then "multipart/mixed"
 	   elseif file
 	     then "application/octet-stream"
 	   elseif (and text (stringp text))
 	     then "text/plain"
 	     else "application/octet-stream"))))
       
     (let ((pos (position #\/ content-type)))
       (if (null pos)
 	  (error "Invalid content-type: ~s" content-type))
       
       (setf type (subseq content-type 0 pos))
       (setf subtype (subseq content-type (1+ pos))))
     
     (setf multipart (equalp type "multipart"))
     (setf textp (or (equalp type "text") (message-rfc822-p type subtype)))
 
     (if* (and subparts (not multipart))
        then (error "subparts may not be specified for non-multipart parts"))
     
     (if* (and (not multipart) (null text) (null file))
        then (error "One of :text or :file must be specified"))
a6202327
 
f6cd6a72
     
a6202327
     ;; Select default charset
f6cd6a72
     (if* (and (null charset) textp)
        then (setf charset 
 	      (or 
 	       (guess-charset-from-ef (find-external-format external-format))
 	       "us-ascii")))
a6202327
 
     ;; For :text, break down to the final usb8.
     (when text
       (setf orig-text text)
       (setf text (if* (stringp text)
 		    then (string-to-octets text :null-terminate nil
 					   :external-format external-format
 					   :start start
 					   :end end)
 		    else (subseq text start end))))
f6cd6a72
     
a6202327
     ;; Select default encoding, which is always base64 except for
     ;; when :text was supplied as a string, in which case we scan to
     ;; choose between 7bit and base64.
f6cd6a72
     (when (and (not multipart) (null encoding))
a6202327
       (setf encoding
 	(if* (and (stringp orig-text) (not (8-bit-array-p text)))
 	   then "7bit"
 	   else "base64")))
   
f6cd6a72
     (setf (mime-part-type part) type)
     (setf (mime-part-subtype part) subtype)
     (setf (mime-part-parameters part) parameters)
     (if* charset
        then (push (cons "charset" charset) (mime-part-parameters part)))
     (setf (mime-part-encoding part) encoding)
     (setf (mime-part-id part) id)
     (setf (mime-part-description part) description)
     (setf (mime-part-headers part) headers)
     
     (if* file
        then (setf (source part) file)
 	    (if* (streamp file)
 	       then (setf (source-type part) :stream)
 	       else (with-open-file (f file)) ;; make sure we can read it.
 		    (setf (source-type part) :file))
 	    (if* (not attachmentp-supplied)
 	       then (setf attachmentp t))
        else (setf (source-type part) :usb8)
a6202327
 	    (setf (source part) text))
f6cd6a72
 
     (if* (and (not textp) (not attachmentp) (not multipart))
        then (setf (disposition part) "inline"))
 
     (when attachmentp
       (setf (disposition part) "attachment")
       (if* (and (null name) file)
 	 then (setf name (excl.osi:basename filepath))))
 
     (if* name
        then (setf (disposition-name part) name))
   
     (if* multipart
        then (let ((boundary (generate-boundary)))
 	      (setf (mime-part-boundary part) boundary)
 	      (push (cons "boundary" boundary) 
 		    (mime-part-parameters part))))
     
     (setf (mime-part-parts part) subparts)
     
     part))
 
a6202327
 (defun 8-bit-array-p (usb8)
   (declare (optimize (speed 3) (safety 0))
 	   ((simple-array (unsigned-byte 8) (*)) usb8))
   (dotimes (n (length usb8))
     (declare (fixnum n))
     (if (> (aref usb8 n) 127)
 	(return t))))
 
f6cd6a72
 (defparameter *ef-nick-to-mime-charset*
     '((:ascii . "us-ascii")
       (:iso-2022-jp . "iso-2022-jp")
       (:koi8-r . "koi8-r")
       (:shiftjis . "shift_jis")
       (:euc . "euc-jp")
       (:gb2312 . "gb2312")
       (:big5 . "big5")
       (:utf8 . "utf-8")))
       
 (defun guess-charset-from-ef (ef)
   (dolist (nick (ef-nicknames (find-external-format ef)))
     (let ((charset (cdr (assoc nick *ef-nick-to-mime-charset*))))
       (if charset (return-from guess-charset-from-ef charset))))
   (let ((ef-name (string-downcase (symbol-name (ef-name (crlf-base-ef ef))))))
     ;; Try iso-8559-x names.
     (multiple-value-bind (found ignore suffix)
a6202327
 	(match-re "^iso8859-(\\d+)-base" ef-name)
f6cd6a72
       (declare (ignore ignore))
       (if found
 	  (return-from guess-charset-from-ef 
 	    (format nil "iso-8859-~a" suffix))))
     
     ;; Try windows- names.
     (multiple-value-bind (found whole value)
a6202327
 	(match-re "^(\\d+)-base$" ef-name)
f6cd6a72
       (declare (ignore whole))
       (if found
 	  (return-from guess-charset-from-ef
 	    (format nil "windows-~a" value))))))
 
 (defmethod mime-part-writer ((part mime-part-constructed) 
 			     &key (stream *terminal-io*))
   (mime-part-constructed-writer part stream t))
 
 (defun mime-part-constructed-writer (part stream top-level)
   (if* top-level
      then (format stream "MIME-Version: 1.0~%"))
   
   ;; First dump user-supplied headers.
   (dolist (h (mime-part-headers part))
     (format stream "~a: ~a~%" (car h) (cdr h)))
 
   ;; Now dump headers that are based on class fields.
 
   (let* ((type (mime-part-type part))
 	 (multipart (equalp type "multipart")))
     (format stream "Content-Type: ~a/~a" type (mime-part-subtype part))
     (dolist (param (mime-part-parameters part))
       (format stream ";~%    ~a=~s" (car param) (cdr param)))
     (format stream "~%")
     
     (if* (mime-part-encoding part)
        then (format stream "Content-Transfer-Encoding: ~a~%" 
 		    (mime-part-encoding part)))
   
     (if* (mime-part-id part)
        then (format stream "Content-Id: ~a~%" (mime-part-id part)))
   
     (if* (mime-part-description part)
        then (format stream "Content-Description: ~a~%" 
 		    (mime-part-description part)))
   
   
     (if* (disposition part)
        then (format stream "Content-Disposition: ~a" (disposition part))
 	    (if* (disposition-name part)
 	       then (format stream ";~%    filename=~s" (disposition-name part)))
 	    (format stream "~%"))
   
     (format stream "~%") ;; terminate headers
   
     (if* multipart 
        then (let ((boundary (mime-part-boundary part)))
 	      (if top-level
 		  (format stream "~
 This is a multi-part message in MIME format.~%"))
 	      (dolist (subpart (mime-part-parts part))
 		(format stream "~%--~a~%" boundary)
 		(mime-part-constructed-writer subpart stream nil))
 	      (format stream "~%--~a--~%" boundary))
        else (let ((instream (if* (eq (source-type part) :stream)
 			       then (source part)
 			     elseif (eq (source-type part) :file)
 			       then (open (source part))
 			       else (make-buffer-input-stream (source part)))))
 	      (unwind-protect
 		  (let ((encoding (mime-part-encoding part)))
 		    (if* (equalp encoding "base64")
5bb4d96e
 		       then (excl::base64-encode-stream instream stream)
f6cd6a72
 		     elseif (equalp encoding "quoted-printable")
 		       then (qp-encode-stream instream stream)
 		       else (raw-encode-stream instream stream)))
 		    ;; cleanup
 		(if* (not (eq (source-type part) :stream))
 		   then (close instream)))))))
 
 (defun mime-part-writer-1 (stream part)
   (mime-part-writer part :stream stream))
 
 (defmacro with-mime-part-constructed-stream ((stream part) &body body)
   `(excl::with-function-input-stream (,stream #'mime-part-writer-1 ,part)
      ,@body))
 
c781d4b2
 
 ;; misc
 
 (defun map-over-parts (part function)
   (funcall function part)
   (if* (multipart-p part)
      then (dolist (p (mime-part-parts part))
 	    (map-over-parts p function))
    elseif (message-rfc822-p (mime-part-type part) (mime-part-subtype part))
      then (map-over-parts (mime-part-message part) function)))
 
ce1d2c5c
 ;;
 
 (defparameter *default-charset-to-ef* 
     '(("us-ascii" . :latin1)
       ("ansi_x3.4-1968" . :latin1)
       ("shift-jis" . :shiftjis)
36132e24
       ("gbk" . :936)
ce1d2c5c
       #+ignore("euc-kr" :iso-2022-kr)))
 
 (defparameter *charset-to-ef* nil)
 
 (defparameter *charset-to-ef-lock* (mp:make-process-lock))
 
 (defparameter *debug-charset-to-ef* nil)
 
 (defun init-charset-to-ef ()
   (let ((ht (make-hash-table :test #'equal)))
     (dolist (pair *default-charset-to-ef*)
       (setf (gethash (car pair) ht) (find-external-format (cdr pair) :errorp nil)))
     (setf *charset-to-ef* ht)))
36132e24
 
 (defun charset-to-external-format (charset)
   (setf charset (string-downcase charset))
ce1d2c5c
   (mp:with-process-lock (*charset-to-ef-lock*)
     (if (null *charset-to-ef*)
 	(init-charset-to-ef))
 
     (macrolet ((save-and-return (ef)
 		 (let ((ef-x (gensym)))
 		   `(let ((,ef-x ,ef))
 		      (progn (setf (gethash charset *charset-to-ef*) ,ef-x)
 			     (return-from charset-to-external-format ,ef-x))))))
       
       (let ((ef (gethash charset *charset-to-ef*)))
 	(if ef 
 	    (return-from charset-to-external-format ef)) ;; Use cached result
 	
 	(if (setf ef (find-external-format charset :errorp nil))
 	    (save-and-return ef))
 	
 	(multiple-value-bind (matched x inner)
 	    (match-re "^windows-(\\d+)$" charset)
 	  (declare (ignore x))
 	  (if (and matched (setf ef (find-external-format inner :errorp nil)))
 	      (save-and-return ef)))
 
 	(multiple-value-bind (matched x dig)
 	    (match-re "^iso-8859-(\\d+)(?:-[ie])?$" charset)
 	  (declare (ignore x))
 	  (if (and matched (setf ef (find-external-format (format nil "iso8859-~a" dig) :errorp nil)))
 	      (save-and-return ef)))
 
 	(if *debug-charset-to-ef*
 	    (format t "no external found for ~a~%" charset))
 	
 	;; No luck
 	nil))))
36132e24
 
 (defun decode-header-text (text)
   (declare (optimize (speed 3))
 	   (string text))
de680a7f
   (when (null text) (error "first argument expected to be non-nil."))
36132e24
   (let ((pos 0)
25a9bbcd
 	(len (length text))
 	last-tail)
36132e24
     (declare (fixnum pos len))
     (with-output-to-string (res)
       (while (< pos len)
25a9bbcd
 	(multiple-value-bind (matched whole charset encoding encoded tail)
 	    (match-re "=\\?([^?]+)\\?(q|b)\\?(.*?)\\?=(\\s+)?" text 
36132e24
 		      :start pos
 		      :case-fold t
 		      :return :index)
 	  
25a9bbcd
 	  (when (null matched)
 	    (when last-tail
 	      (write-string text res
 			    :start (car last-tail) :end (cdr last-tail)))
 	    (return))
36132e24
 	  
 	  ;; Write out the "before" stuff.
 	  (write-string text res :start pos :end (car whole))
 	  
 	  (let* ((charset (subseq text (car charset) (cdr charset)))
 		 (ef (charset-to-external-format charset)))
 	    (if (null ef)
 		(error "No external format found for MIME charset ~s" charset))
 	    (write-string 
 	     (if* (char-equal (char text (car encoding)) #\q)
 		then (qp-decode-string text
 				       :start (car encoded)
 				       :end (cdr encoded)
25a9bbcd
 				       :external-format ef
 				       :underscores-are-spaces t)
36132e24
 		else ;; FIXME: Clean this up with/if rfe6174 is completed.
 		     (octets-to-string
 		      (base64-string-to-usb8-array 
 		       (subseq text (car encoded) (cdr encoded)))
 		      :external-format ef))
 	     res))
 	  
25a9bbcd
 	  (setf pos (cdr whole))
 	  (setf last-tail tail)))
36132e24
 	  
       ;; Write out the remaining portion.
       (write-string text res :start pos))))
 
 
f6cd6a72
 ;; Stuff ripped off from aserve
 
 (defun split-namestring (file)
   ;; split the namestring into root and tail and then the tail
   ;; into name and type
   ;; 
   ;; any of the return value can be nil if the corresponding item
   ;; isn't present.
   ;;
   ;; rules for splitting the tail into name and type components:
   ;;  if the last period in the tail is at the beginning or end of the
   ;;  tail, then the name is exactly the tail and type is nil.
   ;;  Thus .foo and bar.  are just names, no type
   ;;  but .foo.c  has a name of ".foo" and a type of "c"
   ;;  Thus if there is a non-nil type then it means that 
   ;;    1. there will be a non nil name as well
   ;;    2. to reconstruct the filename you need to add a period between
   ;;       the name and type.
   ;;
   (let ((pos (min (or (or (position #\/ file :from-end t) most-positive-fixnum)
 		      #+mswindows (position #\\ file :from-end t))))
 	root
 	tail)
     
     (if* (equal file "") then (return-from split-namestring nil))
     
     (if* (and pos (< pos most-positive-fixnum))
        then ; we have root and tail
 	    (if* (eql pos (1- (length file)))
 	       then ; just have root
 		    (return-from split-namestring
 		      (values file nil nil nil)))
 	    
     
 	    (setq root (subseq file 0 (1+ pos))
 		  tail (subseq file (1+ pos)))
        else (setq tail file))
     
     
     ; split the tail
     (let ((pos (position #\. tail :from-end t)))
       (if* (or (null pos)
 	       (zerop pos)
 	       (equal pos (1- (length tail))))
 	 then ; name begins or ends with . so it's not
 	      ; a type separator
 	      (values root tail tail nil)
 	 else ; have all pieces
 	      (values root tail
 		      (subseq tail 0 pos)
 		      (subseq tail (1+ pos)))))))
 
 
 ; we can specify either an exact url or one that handles all
 ; urls with a common prefix.
 ;;
 ;; if the prefix is given as a list: e.g. ("ReadMe") then it says that
 ;; this mime type applie to file named ReadMe.  Note that file types
 ;; are checked first and if no match then a filename match is done.
 ;
 (defparameter *file-type-to-mime-type*
     ;; this list constructed by generate-mime-table in parse.cl
     '(("application/EDI-Consent") ("application/EDI-X12") ("application/EDIFACT")
       ("application/activemessage") ("application/andrew-inset" "ez")
       ("application/applefile") ("application/atomicmail")
       ("application/batch-SMTP") ("application/beep+xml") ("application/cals-1840")
       ("application/commonground") ("application/cybercash")
       ("application/dca-rft") ("application/dec-dx") ("application/dvcs")
       ("application/eshop") ("application/http") ("application/hyperstudio")
       ("application/iges") ("application/index") ("application/index.cmd")
       ("application/index.obj") ("application/index.response")
       ("application/index.vnd") ("application/iotp") ("application/ipp")
       ("application/isup") ("application/font-tdpfr")
       ("application/mac-binhex40" "hqx") ("application/mac-compactpro" "cpt")
       ("application/macwriteii") ("application/marc") ("application/mathematica")
       ("application/mathematica-old") ("application/msword" "doc")
       ("application/news-message-id") ("application/news-transmission")
       ("application/ocsp-request") ("application/ocsp-response")
       ("application/octet-stream" "bin" "dms" "lha" "lzh" "exe" "class" "so" "dll"
        "img" "iso")
       ("application/ogg" "ogg") ("application/parityfec") ("application/pdf" "pdf")
       ("application/pgp-encrypted") ("application/pgp-keys")
       ("application/pgp-signature") ("application/pkcs10")
       ("application/pkcs7-mime") ("application/pkcs7-signature")
       ("application/pkix-cert") ("application/pkix-crl") ("application/pkixcmp")
       ("application/postscript" "ai" "eps" "ps")
       ("application/prs.alvestrand.titrax-sheet") ("application/prs.cww")
       ("application/prs.nprend") ("application/qsig")
       ("application/remote-printing") ("application/riscos")
       ("application/rtf" "rtf") ("application/sdp") ("application/set-payment")
       ("application/set-payment-initiation") ("application/set-registration")
       ("application/set-registration-initiation") ("application/sgml")
       ("application/sgml-open-catalog") ("application/sieve") ("application/slate")
       ("application/smil" "smi" "smil") ("application/timestamp-query")
       ("application/timestamp-reply") ("application/vemmi")
       ("application/vnd.3M.Post-it-Notes") ("application/vnd.FloGraphIt")
       ("application/vnd.accpac.simply.aso") ("application/vnd.accpac.simply.imp")
       ("application/vnd.acucobol") ("application/vnd.aether.imp")
       ("application/vnd.anser-web-certificate-issue-initiation")
       ("application/vnd.anser-web-funds-transfer-initiation")
       ("application/vnd.audiograph") ("application/vnd.businessobjects")
       ("application/vnd.bmi") ("application/vnd.canon-cpdl")
       ("application/vnd.canon-lips") ("application/vnd.claymore")
       ("application/vnd.commerce-battelle") ("application/vnd.commonspace")
       ("application/vnd.comsocaller") ("application/vnd.contact.cmsg")
       ("application/vnd.cosmocaller") ("application/vnd.cups-postscript")
       ("application/vnd.cups-raster") ("application/vnd.cups-raw")
       ("application/vnd.ctc-posml") ("application/vnd.cybank")
       ("application/vnd.dna") ("application/vnd.dpgraph") ("application/vnd.dxr")
       ("application/vnd.ecdis-update") ("application/vnd.ecowin.chart")
       ("application/vnd.ecowin.filerequest") ("application/vnd.ecowin.fileupdate")
       ("application/vnd.ecowin.series") ("application/vnd.ecowin.seriesrequest")
       ("application/vnd.ecowin.seriesupdate") ("application/vnd.enliven")
       ("application/vnd.epson.esf") ("application/vnd.epson.msf")
       ("application/vnd.epson.quickanime") ("application/vnd.epson.salt")
       ("application/vnd.epson.ssf") ("application/vnd.ericsson.quickcall")
       ("application/vnd.eudora.data") ("application/vnd.fdf")
       ("application/vnd.ffsns") ("application/vnd.framemaker")
       ("application/vnd.fsc.weblaunch") ("application/vnd.fujitsu.oasys")
       ("application/vnd.fujitsu.oasys2") ("application/vnd.fujitsu.oasys3")
       ("application/vnd.fujitsu.oasysgp") ("application/vnd.fujitsu.oasysprs")
       ("application/vnd.fujixerox.ddd") ("application/vnd.fujixerox.docuworks")
       ("application/vnd.fujixerox.docuworks.binder") ("application/vnd.fut-misnet")
       ("application/vnd.grafeq") ("application/vnd.groove-account")
       ("application/vnd.groove-identity-message")
       ("application/vnd.groove-injector") ("application/vnd.groove-tool-message")
       ("application/vnd.groove-tool-template") ("application/vnd.groove-vcard")
       ("application/vnd.hhe.lesson-player") ("application/vnd.hp-HPGL")
       ("application/vnd.hp-PCL") ("application/vnd.hp-PCLXL")
       ("application/vnd.hp-hpid") ("application/vnd.hp-hps")
       ("application/vnd.httphone") ("application/vnd.hzn-3d-crossword")
       ("application/vnd.ibm.afplinedata") ("application/vnd.ibm.MiniPay")
       ("application/vnd.ibm.modcap") ("application/vnd.informix-visionary")
       ("application/vnd.intercon.formnet") ("application/vnd.intertrust.digibox")
       ("application/vnd.intertrust.nncp") ("application/vnd.intu.qbo")
       ("application/vnd.intu.qfx") ("application/vnd.irepository.package+xml")
       ("application/vnd.is-xpr") ("application/vnd.japannet-directory-service")
       ("application/vnd.japannet-jpnstore-wakeup")
       ("application/vnd.japannet-payment-wakeup")
       ("application/vnd.japannet-registration")
       ("application/vnd.japannet-registration-wakeup")
       ("application/vnd.japannet-setstore-wakeup")
       ("application/vnd.japannet-verification")
       ("application/vnd.japannet-verification-wakeup") ("application/vnd.koan")
       ("application/vnd.lotus-1-2-3") ("application/vnd.lotus-approach")
       ("application/vnd.lotus-freelance") ("application/vnd.lotus-notes")
       ("application/vnd.lotus-organizer") ("application/vnd.lotus-screencam")
       ("application/vnd.lotus-wordpro") ("application/vnd.mcd")
       ("application/vnd.mediastation.cdkey") ("application/vnd.meridian-slingshot")
       ("application/vnd.mif" "mif") ("application/vnd.minisoft-hp3000-save")
       ("application/vnd.mitsubishi.misty-guard.trustweb")
       ("application/vnd.mobius.daf") ("application/vnd.mobius.dis")
       ("application/vnd.mobius.msl") ("application/vnd.mobius.plc")
       ("application/vnd.mobius.txf") ("application/vnd.motorola.flexsuite")
       ("application/vnd.motorola.flexsuite.adsi")
       ("application/vnd.motorola.flexsuite.fis")
       ("application/vnd.motorola.flexsuite.gotap")
       ("application/vnd.motorola.flexsuite.kmr")
       ("application/vnd.motorola.flexsuite.ttc")
       ("application/vnd.motorola.flexsuite.wem")
       ("application/vnd.mozilla.xul+xml") ("application/vnd.ms-artgalry")
       ("application/vnd.ms-asf") ("application/vnd.ms-excel" "xls")
       ("application/vnd.ms-lrm") ("application/vnd.ms-powerpoint" "ppt")
       ("application/vnd.ms-project") ("application/vnd.ms-tnef")
       ("application/vnd.ms-works") ("application/vnd.mseq")
       ("application/vnd.msign") ("application/vnd.music-niff")
       ("application/vnd.musician") ("application/vnd.netfpx")
       ("application/vnd.noblenet-directory") ("application/vnd.noblenet-sealer")
       ("application/vnd.noblenet-web") ("application/vnd.novadigm.EDM")
       ("application/vnd.novadigm.EDX") ("application/vnd.novadigm.EXT")
       ("application/vnd.osa.netdeploy") ("application/vnd.palm")
       ("application/vnd.pg.format") ("application/vnd.pg.osasli")
       ("application/vnd.powerbuilder6") ("application/vnd.powerbuilder6-s")
       ("application/vnd.powerbuilder7") ("application/vnd.powerbuilder7-s")
       ("application/vnd.powerbuilder75") ("application/vnd.powerbuilder75-s")
       ("application/vnd.previewsystems.box")
       ("application/vnd.publishare-delta-tree") ("application/vnd.pvi.ptid1")
       ("application/vnd.pwg-xhtml-print+xml") ("application/vnd.rapid")
       ("application/vnd.s3sms") ("application/vnd.seemail")
       ("application/vnd.shana.informed.formdata")
       ("application/vnd.shana.informed.formtemplate")
       ("application/vnd.shana.informed.interchange")
       ("application/vnd.shana.informed.package") ("application/vnd.sss-cod")
       ("application/vnd.sss-dtf") ("application/vnd.sss-ntf")
       ("application/vnd.sun.xml.writer" "sxw")
       ("application/vnd.sun.xml.writer.template" "stw")
       ("application/vnd.sun.xml.calc" "sxc")
       ("application/vnd.sun.xml.calc.template" "stc")
       ("application/vnd.sun.xml.draw" "sxd")
       ("application/vnd.sun.xml.draw.template" "std")
       ("application/vnd.sun.xml.impress" "sxi")
       ("application/vnd.sun.xml.impress.template" "sti")
       ("application/vnd.sun.xml.writer.global" "sxg")
       ("application/vnd.sun.xml.math" "sxm") ("application/vnd.street-stream")
       ("application/vnd.svd") ("application/vnd.swiftview-ics")
       ("application/vnd.triscape.mxs") ("application/vnd.trueapp")
       ("application/vnd.truedoc") ("application/vnd.tve-trigger")
       ("application/vnd.ufdl") ("application/vnd.uplanet.alert")
       ("application/vnd.uplanet.alert-wbxml")
       ("application/vnd.uplanet.bearer-choice-wbxml")
       ("application/vnd.uplanet.bearer-choice") ("application/vnd.uplanet.cacheop")
       ("application/vnd.uplanet.cacheop-wbxml") ("application/vnd.uplanet.channel")
       ("application/vnd.uplanet.channel-wbxml") ("application/vnd.uplanet.list")
       ("application/vnd.uplanet.list-wbxml") ("application/vnd.uplanet.listcmd")
       ("application/vnd.uplanet.listcmd-wbxml") ("application/vnd.uplanet.signal")
       ("application/vnd.vcx") ("application/vnd.vectorworks")
       ("application/vnd.vidsoft.vidconference") ("application/vnd.visio")
       ("application/vnd.vividence.scriptfile") ("application/vnd.wap.sic")
       ("application/vnd.wap.slc") ("application/vnd.wap.wbxml" "wbxml")
       ("application/vnd.wap.wmlc" "wmlc")
       ("application/vnd.wap.wmlscriptc" "wmlsc") ("application/vnd.webturbo")
       ("application/vnd.wrq-hp3000-labelled") ("application/vnd.wt.stf")
       ("application/vnd.xara") ("application/vnd.xfdl")
       ("application/vnd.yellowriver-custom-menu") ("application/whoispp-query")
       ("application/whoispp-response") ("application/wita")
       ("application/wordperfect5.1") ("application/x-bcpio" "bcpio")
       ("application/x-bittorrent" "torrent") ("application/x-bzip2" "bz2")
       ("application/x-cdlink" "vcd") ("application/x-chess-pgn" "pgn")
       ("application/x-compress") ("application/x-cpio" "cpio")
       ("application/x-csh" "csh") ("application/x-director" "dcr" "dir" "dxr")
       ("application/x-dvi" "dvi") ("application/x-futuresplash" "spl")
       ("application/x-gtar" "gtar") ("application/x-gzip" "gz" "tgz")
       ("application/x-hdf" "hdf") ("application/x-javascript" "js")
       ("application/x-kword" "kwd" "kwt") ("application/x-kspread" "ksp")
       ("application/x-kpresenter" "kpr" "kpt") ("application/x-kchart" "chrt")
       ("application/x-killustrator" "kil")
       ("application/x-koan" "skp" "skd" "skt" "skm")
       ("application/x-latex" "latex") ("application/x-netcdf" "nc" "cdf")
       ("application/x-rpm" "rpm") ("application/x-sh" "sh")
       ("application/x-shar" "shar") ("application/x-shockwave-flash" "swf")
       ("application/x-stuffit" "sit") ("application/x-sv4cpio" "sv4cpio")
       ("application/x-sv4crc" "sv4crc") ("application/x-tar" "tar")
       ("application/x-tcl" "tcl") ("application/x-tex" "tex")
       ("application/x-texinfo" "texinfo" "texi")
       ("application/x-troff" "t" "tr" "roff") ("application/x-troff-man" "man")
       ("application/x-troff-me" "me") ("application/x-troff-ms" "ms")
       ("application/x-ustar" "ustar") ("application/x-wais-source" "src")
       ("application/x400-bp") ("application/xhtml+xml" "xhtml" "xht")
       ("application/xml") ("application/xml-dtd")
       ("application/xml-external-parsed-entity") ("application/zip" "zip")
       ("audio/32kadpcm") ("audio/basic" "au" "snd") ("audio/g.722.1") ("audio/l16")
       ("audio/midi" "mid" "midi" "kar") ("audio/mp4a-latm") ("audio/mpa-robust")
       ("audio/mpeg" "mpga" "mp2" "mp3") ("audio/parityfec") ("audio/prs.sid")
       ("audio/telephone-event") ("audio/tone") ("audio/vnd.cisco.nse")
       ("audio/vnd.cns.anp1") ("audio/vnd.cns.inf1") ("audio/vnd.digital-winds")
       ("audio/vnd.everad.plj") ("audio/vnd.lucent.voice") ("audio/vnd.nortel.vbk")
       ("audio/vnd.nuera.ecelp4800") ("audio/vnd.nuera.ecelp7470")
       ("audio/vnd.nuera.ecelp9600") ("audio/vnd.octel.sbc") ("audio/vnd.qcelp")
       ("audio/vnd.rhetorex.32kadpcm") ("audio/vnd.vmx.cvsd")
       ("audio/x-aiff" "aif" "aiff" "aifc") ("audio/x-mpegurl" "m3u")
       ("audio/x-pn-realaudio" "ram" "rm") ("audio/x-realaudio" "ra")
       ("audio/x-wav" "wav") ("chemical/x-pdb" "pdb") ("chemical/x-xyz" "xyz")
       ("image/bmp" "bmp") ("image/cgm") ("image/g3fax") ("image/gif" "gif")
       ("image/ief" "ief") ("image/jpeg" "jpeg" "jpg" "jpe") ("image/naplps")
       ("image/png" "png") ("image/prs.btif") ("image/prs.pti")
       ("image/tiff" "tiff" "tif") ("image/vnd.cns.inf2")
       ("image/vnd.djvu" "djvu" "djv") ("image/vnd.dwg") ("image/vnd.dxf")
       ("image/vnd.fastbidsheet") ("image/vnd.fpx") ("image/vnd.fst")
       ("image/vnd.fujixerox.edmics-mmr") ("image/vnd.fujixerox.edmics-rlc")
       ("image/vnd.mix") ("image/vnd.net-fpx") ("image/vnd.svf")
       ("image/vnd.wap.wbmp" "wbmp") ("image/vnd.xiff") ("image/x-cmu-raster" "ras")
       ("image/x-portable-anymap" "pnm") ("image/x-portable-bitmap" "pbm")
       ("image/x-portable-graymap" "pgm") ("image/x-portable-pixmap" "ppm")
       ("image/x-rgb" "rgb") ("image/x-xbitmap" "xbm") ("image/x-xpixmap" "xpm")
       ("image/x-xwindowdump" "xwd") ("message/delivery-status")
       ("message/disposition-notification") ("message/external-body")
       ("message/http") ("message/news") ("message/partial") ("message/rfc822")
       ("message/s-http") ("model/iges" "igs" "iges")
       ("model/mesh" "msh" "mesh" "silo") ("model/vnd.dwf")
       ("model/vnd.flatland.3dml") ("model/vnd.gdl") ("model/vnd.gs-gdl")
       ("model/vnd.gtw") ("model/vnd.mts") ("model/vnd.vtu")
       ("model/vrml" "wrl" "vrml") ("multipart/alternative")
       ("multipart/appledouble") ("multipart/byteranges") ("multipart/digest")
       ("multipart/encrypted") ("multipart/form-data") ("multipart/header-set")
       ("multipart/mixed") ("multipart/parallel") ("multipart/related")
       ("multipart/report") ("multipart/signed") ("multipart/voice-message")
       ("text/calendar") ("text/css" "css") ("text/directory") ("text/enriched")
       ("text/html" "html" "htm") ("text/parityfec") ("text/plain" "asc" "txt")
       ("text/prs.lines.tag") ("text/rfc822-headers") ("text/richtext" "rtx")
       ("text/rtf" "rtf") ("text/sgml" "sgml" "sgm")
       ("text/tab-separated-values" "tsv") ("text/t140") ("text/uri-list")
       ("text/vnd.DMClientScript") ("text/vnd.IPTC.NITF") ("text/vnd.IPTC.NewsML")
       ("text/vnd.abc") ("text/vnd.curl") ("text/vnd.flatland.3dml")
       ("text/vnd.fly") ("text/vnd.fmi.flexstor") ("text/vnd.in3d.3dml")
       ("text/vnd.in3d.spot") ("text/vnd.latex-z") ("text/vnd.motorola.reflex")
       ("text/vnd.ms-mediapackage") ("text/vnd.wap.si") ("text/vnd.wap.sl")
       ("text/vnd.wap.wml" "wml") ("text/vnd.wap.wmlscript" "wmls")
       ("text/x-setext" "etx") ("text/xml" "xml" "xsl")
       ("text/xml-external-parsed-entity") ("video/mp4v-es")
       ("video/mpeg" "mpeg" "mpg" "mpe") ("video/parityfec") ("video/pointer")
       ("video/quicktime" "qt" "mov") ("video/vnd.fvt") ("video/vnd.motorola.video")
       ("video/vnd.motorola.videop") ("video/vnd.mpegurl" "mxu") ("video/vnd.mts")
       ("video/vnd.nokia.interleaved-multimedia") ("video/vnd.vivo")
       ("video/x-msvideo" "avi") ("video/x-sgi-movie" "movie")
       ("x-conference/x-cooltalk" "ice")))
 
 (defvar *mime-types* nil)
 
 (defun build-mime-types-table ()
   (if* (null *mime-types*)
      then (setf *mime-types* (make-hash-table :test #'equalp))
 	  (dolist (ent *file-type-to-mime-type*)
 	    (dolist (type (cdr ent))
 	      (setf (gethash type *mime-types*) (car ent))))))
   
 
 (build-mime-types-table)  ;; build the table now
 
 ;; return mime type if known
 (defmethod lookup-mime-type (filename) 
   (if* (pathnamep filename)
      then (setq filename (namestring filename)))
   (multiple-value-bind (root tail name type)
       (split-namestring filename)
     (declare (ignore root name))
     (if* (and type (gethash type *mime-types*))
        thenret
      elseif (gethash (list tail) *mime-types*) 
        thenret)))