2 ;;; A multilingual MIME message header encoder/decoder.
3 ;;; by Morioka Tomohiko (morioka@jaist.ac.jp)
5 ;;; original MIME decoder is
6 ;;; mime.el,v 1.5 1992/07/18 07:52:08 by Enami Tsugutomo
16 (if (not (fboundp 'member))
24 "$Id: tiny-mime.el,v 5.11 1995/04/18 12:28:22 morioka Exp $")
26 (defconst mime/tiny-mime-version (get-version-string mime/RCS-ID))
29 ;;; @ MIME encoded-word definition
32 (defconst mime/charset-regexp "[A-Za-z0-9!#$%&'*+---^_`{}|~]")
33 (defconst mime/encoded-text-regexp "[!->@-~]+")
35 (defconst mime/Base64-token-regexp "[A-Za-z0-9+/=]")
36 (defconst mime/Base64-encoded-text-regexp
38 mime/Base64-token-regexp
39 mime/Base64-token-regexp
40 mime/Base64-token-regexp
41 mime/Base64-token-regexp
43 (defconst mime/Base64-encoding-and-encoded-text-regexp
44 (concat "\\(B\\)\\?" mime/Base64-encoded-text-regexp))
46 (defconst mime/Quoted-Printable-hex-char-regexp "[0123456789ABCDEF]")
47 (defconst mime/Quoted-Printable-octet-regexp
49 mime/Quoted-Printable-hex-char-regexp
50 mime/Quoted-Printable-hex-char-regexp))
51 (defconst mime/Quoted-Printable-encoded-text-regexp
52 (concat "\\([^=?]\\|" mime/Quoted-Printable-octet-regexp "\\)+"))
53 (defconst mime/Quoted-Printable-encoding-and-encoded-text-regexp
54 (concat "\\(Q\\)\\?" mime/Quoted-Printable-encoded-text-regexp))
56 (defconst mime/encoded-word-regexp (concat (regexp-quote "=?")
64 mime/encoded-text-regexp
68 (defun mime/nth-string (s n)
70 (substring s (match-beginning n) (match-end n))
71 (buffer-substring (match-beginning n) (match-end n))))
73 (defun mime/encoded-word-charset (str)
74 (mime/nth-string str 1))
76 (defun mime/encoded-word-encoding (str)
77 (mime/nth-string str 2))
79 (defun mime/encoded-word-encoded-text (str)
80 (mime/nth-string str 3))
82 (defun mime/rest-of-string (str)
84 (substring str (match-end 0))
85 (buffer-substring (match-end 0)(point-max))
92 (defvar mime/no-encoding-header-fields '("X-Nsubject"))
94 (defvar mime/use-X-Nsubject nil)
97 ;;; @ compatible module among Mule, NEmacs and NEpoch
99 (cond ((boundp 'MULE) (require 'tm-mule))
100 ((boundp 'NEMACS)(require 'tm-nemacs))
101 (t (require 'tm-orig))
105 ;;; @ Application Interface
108 ;;; @@ MIME header decoders
112 (defun mime/decode-encoded-word (word)
113 (if (string-match mime/encoded-word-regexp word)
114 (let ((charset (upcase (mime/encoded-word-charset word)))
115 (encoding (mime/encoded-word-encoding word))
116 (text (mime/encoded-word-encoded-text word)))
117 (mime/decode-encoded-text charset encoding text))
120 (defun mime/decode-region (beg end)
124 (narrow-to-region beg end)
125 (goto-char (point-min))
126 (let (charset encoding text)
127 (while (re-search-forward mime/encoded-word-regexp nil t)
128 (insert (mime/decode-encoded-word
130 (buffer-substring (match-beginning 0) (match-end 0))
131 (delete-region (match-beginning 0) (match-end 0))
137 (defun mime/decode-message-header ()
141 (narrow-to-region (goto-char (point-min))
142 (progn (re-search-forward "^$" nil t) (point)))
143 (mime/prepare-decode-message-header)
144 (mime/decode-region (point-min) (point-max))
147 (defun mime/decode-string (str)
148 (let ((dest "")(ew nil)
150 (while (setq beg (string-match mime/encoded-word-regexp str))
152 (if (not (and (eq ew t) (string= (substring str 0 beg) " ")))
153 (setq dest (concat dest (substring str 0 beg)
157 (setq end (match-end 0))
158 (setq dest (concat dest (mime/decode-encoded-word (substring str beg end))
160 (setq str (substring str end))
166 ;;; @@ MIME header encoders
169 (defun mime/encode-string (string encoding &optional mode)
170 (cond ((equal encoding "B") (mime/base64-encode-string string))
171 ((equal encoding "Q") (mime/Quoted-Printable-encode-string string mode))
175 (defun mime/encode-field (str)
176 (setq str (message/unfolding-string str))
177 (let ((ret (message/divide-field str))
178 field-name field-body)
179 (setq field-name (car ret))
180 (setq field-body (nth 1 ret))
181 (concat field-name " "
182 (cond ((string= field-body "") "")
183 ((or (string-match "^Reply-To:$" field-name)
184 (string-match "^From:$" field-name)
185 (string-match "^Sender:$" field-name)
186 (string-match "^Resent-Reply-To:$" field-name)
187 (string-match "^Resent-From:$" field-name)
188 (string-match "^Resent-Sender:$" field-name)
189 (string-match "^To:$" field-name)
190 (string-match "^Resent-To:$" field-name)
191 (string-match "^cc:$" field-name)
192 (string-match "^Resent-cc:$" field-name)
193 (string-match "^bcc:$" field-name)
194 (string-match "^Resent-bcc:$" field-name)
196 (mime/encode-address-list
197 (+ (length field-name) 1) field-body)
201 (let ((r mime/no-encoding-header-fields) fn)
204 (if (string-match (concat "^" fn ":$") field-name)
205 (throw 'tag field-body)
209 (nth 1 (mime/encode-header-string
210 (+ (length field-name) 1) field-body))
215 (defun mime/encode-message-header ()
219 (narrow-to-region (goto-char (point-min))
222 (concat "^" (regexp-quote mail-header-separator) "$")
226 (goto-char (point-min))
228 (while (re-search-forward "^.+:.*\\(\n\\s +.*\\)*" nil t)
229 (setq beg (match-beginning 0))
230 (setq end (match-end 0))
231 (setq field (buffer-substring beg end))
232 (insert (mime/encode-field
234 (buffer-substring beg end)
235 (delete-region beg end)
238 (if mime/use-X-Nsubject
240 (goto-char (point-min))
241 (if (re-search-forward "^Subject:.*\\(\n\\s +.*\\)*" nil t)
242 (let ((str (buffer-substring (match-beginning 0)(match-end 0))))
243 (if (string-match mime/encoded-word-regexp str)
246 (nth 1 (message/divide-field
248 (message/unfolding-string str))
254 ;;; @ Base64 (B-encode) decoder/encoder
255 ;;; by Enami Tsugutomo
258 (defun mime/base64-decode-string (string)
259 (mime/base64-mapconcat (function mime/base64-decode-chars) 4 string))
261 ;; (mime/base64-encode-string (mime/base64-decode-string "GyRAOjRGI0stGyhK"))
262 (defun mime/base64-encode-string (string &optional mode)
263 (let ((es (mime/base64-mapconcat (function mime/base64-encode-chars) 3 string))
265 (setq m (mod (length es) 4))
272 ;; (char-to-string (mime/base64-bit-to-char 26))
273 (defun mime/base64-bit-to-char (n)
274 (cond ((eq n nil) ?=)
276 ((< n 52) (+ ?a (- n 26)))
277 ((< n 62) (+ ?0 (- n 52)))
280 (t (error "not a base64 integer %d" n))))
282 (defun mime/base64-char-to-bit (c)
283 (cond ((and (<= ?A c) (<= c ?Z)) (- c ?A))
284 ((and (<= ?a c) (<= c ?z)) (+ (- c ?a) 26))
285 ((and (<= ?0 c) (<= c ?9)) (+ (- c ?0) 52))
289 (t (error "not a base64 character %c" c))))
291 (defun mime/mask (i n) (logand i (1- (ash 1 n))))
293 (defun mime/base64-encode-1 (a &optional b &optional c)
295 (cons (logior (ash (mime/mask a 2) (- 6 2))
298 (cons (logior (ash (mime/mask b 4) (- 6 4))
301 (cons (mime/mask c (- 6 0))
304 (defun mime/base64-decode-1 (a b &optional c &optional d)
305 (cons (logior (ash a 2) (ash b (- 2 6)))
306 (if c (cons (logior (ash (mime/mask b 4) 4)
307 (mime/mask (ash c (- 4 6)) 4))
308 (if d (cons (logior (ash (mime/mask c 2) 6) d)
311 ;; (mime/base64-decode-chars ?G ?y ?R ?A)
312 (defun mime/base64-decode-chars (a b c d)
313 (apply (function mime/base64-decode-1)
314 (mapcar (function mime/base64-char-to-bit)
317 ;; (mapcar (function char-to-string) (mime/base64-encode-chars 27 36 64))
318 (defun mime/base64-encode-chars (a b c)
319 (mapcar (function mime/base64-bit-to-char) (mime/base64-encode-1 a b c)))
321 (defun mime/base64-fecth-from (func from pos len)
325 ret (cons (funcall func from (+ pos len)) ret)))
328 (defun mime/base64-fecth-from-buffer (from pos len)
329 (mime/base64-fecth-from (function (lambda (f p) (char-after p)))
332 (defun mime/base64-fecth-from-string (from pos len)
333 (mime/base64-fecth-from (function (lambda (f p)
334 (if (< p (length f)) (aref f p))))
337 (defun mime/base64-fecth (source pos len)
338 (cond ((stringp source) (mime/base64-fecth-from-string source pos len))
339 (t (mime/base64-fecth-from-buffer source pos len))))
341 (defun mime/base64-mapconcat (func unit string)
343 (while (< i (length string))
345 (apply (function concat)
347 (mapcar (function char-to-string)
348 (apply func (mime/base64-fecth string i unit)))))
352 ;;; @ Quoted-Printable (Q-encode) encoder/decoder
355 (defun mime/Quoted-Printable-decode-string (str)
360 (setq chr (elt str i))
364 (setq h (hex-char-to-number (elt str (+ i 1))))
365 (setq l (hex-char-to-number (elt str (+ i 2))))
366 (setq num (+ (* h 16) l))
367 (setq dest (concat dest (char-to-string num)))
371 (setq dest (concat dest (char-to-string chr)))
375 (setq dest (concat dest (char-to-string 32)))
379 (setq dest (concat dest (char-to-string chr)))
385 (defun mime/Quoted-Printable-encode-string (str &optional mode)
392 (setq chr (elt str i))
394 (setq dest (concat dest "_"))
399 (and (eq mode 'comment)
404 (and (eq mode 'phrase)
405 (not (string-match "[A-Za-z0-9!*+/=_---]"
406 (char-to-string chr)))
410 (setq dest (concat dest
412 (char-to-string (number-to-hex-char (/ chr 16)))
413 (char-to-string (number-to-hex-char (% chr 16)))
416 (t (setq dest (concat dest (char-to-string chr)))
422 ;;; @ functions for message header encoding
425 (defun mime/encode-and-split-string (n string charset encoding)
427 (len (length string))
428 (js (mime/convert-string-from-emacs string charset))
429 (cesl (+ (length charset) (length encoding) 6 ))
431 (setq ewl (mime/encoded-word-length js encoding))
434 (setq m (+ n ewl cesl))
437 (while (and (< i len)
438 (setq js (mime/convert-string-from-emacs
439 (substring string 0 i) charset))
440 (setq m (+ n (mime/encoded-word-length js encoding) cesl))
443 (setq i (+ i (char-bytes (elt string i))))
445 (setq js (mime/convert-string-from-emacs
446 (substring string 0 j) charset))
447 (setq m (+ n (mime/encoded-word-length js encoding) cesl))
448 (setq rest (substring string j))
453 (list m (concat "=?" charset "?" encoding "?"
454 (mime/encode-string js encoding)
459 (defun mime/encode-header-word (n string charset encoding)
460 (let (dest str ret m)
461 (if (null (setq ret (mime/encode-and-split-string n string charset encoding)))
464 (setq dest (nth 1 ret))
466 (setq str (nth 2 ret))
467 (while (and (stringp str)
468 (setq ret (mime/encode-and-split-string 1 str charset encoding))
470 (setq dest (concat dest "\n " (nth 1 ret)))
472 (setq str (nth 2 ret))
478 (defun mime/encode-header-string (n string &optional mode)
479 (if (string= string "")
481 (let ((ssl (mime/separate-string-for-encoder string))
482 i len cell et w ew (dest "") b l)
483 (setq len (length ssl))
484 (setq cell (nth 0 ssl))
486 ;; string-width crashes when the argument is nil,
487 ;; so replace the argument
488 ;; (original modification by Kenji Rikitake 9-JAN-1995)
489 (setq w (or (cdr cell) ""))
492 (if (> (+ n (string-width w)) 76)
494 (setq dest (concat dest "\n "))
498 (setq dest (concat dest w))
499 (setq b (+ b (string-width w)))
502 (setq ew (mime/encode-header-word n (cdr cell) (car et) (cdr et)))
503 (setq dest (nth 1 ew))
508 (setq cell (nth i ssl))
511 (cond ((string-match "^[ \t]*$" w)
512 (setq b (+ b (string-width (cdr cell))))
513 (setq dest (concat dest (cdr cell)))
516 (if (> (+ b (string-width w)) 76)
518 (if (eq (elt dest (- (length dest) 1)) 32)
519 (setq dest (substring dest 0 (- (length dest) 1)))
521 (setq dest (concat dest "\n " w))
522 (setq b (+ (length w) 1))
524 (setq l (length dest))
526 (eq (elt dest (- l 2)) ?\?)
527 (eq (elt dest (- l 1)) ?=)
530 (setq dest (concat dest " "))
533 (setq dest (concat dest w))
534 (setq b (+ b (string-width w)))
537 (if (not (eq (elt dest (- (length dest) 1)) 32))
539 (setq dest (concat dest " "))
543 (mime/encode-header-word b (cdr cell) (car et) (cdr et)))
545 (if (string-match "^\n" (nth 1 ew))
546 (setq dest (concat (substring dest 0 (- (length dest) 1))
548 (setq dest (concat dest (nth 1 ew)))
556 (defun mime/encode-address-list (n str)
557 (let* ((ret (message/parse-addresses str))
558 (r ret) cell en-ret j cl (dest "") s)
561 (cond ((string= (nth 1 cell) "<")
562 (setq en-ret (mime/encode-header-string n (nth 0 cell) 'phrase))
563 (setq dest (concat dest (nth 1 en-ret)))
564 (setq n (car en-ret))
567 (mime/encode-header-string
568 n (concat (nth 1 cell)(nth 2 cell)(nth 3 cell) ", ")))
569 (setq en-ret (mime/encode-header-string
570 n (concat (nth 1 cell)(nth 2 cell)(nth 3 cell))))
572 (if (and (eq (elt (nth 1 en-ret) 0) ?\n)
573 (eq (elt dest (- (length dest) 1)) 32))
574 (setq dest (substring dest 0 (- (length dest) 1)))
576 (setq dest (concat dest (nth 1 en-ret)))
577 (setq n (car en-ret))
580 (setq en-ret (mime/encode-header-string n (nth 0 cell)))
581 (setq dest (concat dest (nth 1 en-ret)))
582 (setq n (car en-ret))
584 (setq en-ret (mime/encode-header-string (+ n 2) (nth 2 cell)
586 (if (eq (elt (nth 1 en-ret) 0) ?\n)
588 (setq dest (concat dest "\n ("))
589 (setq en-ret (mime/encode-header-string 2 (nth 2 cell)
593 (setq dest (concat dest " ("))
595 (setq dest (concat dest (nth 1 en-ret)))
596 (setq n (car en-ret))
599 (mime/encode-header-string n (concat (nth 3 cell) ", "))
601 (setq en-ret (mime/encode-header-string n (nth 3 cell)))
603 (setq dest (concat dest (nth 1 en-ret)))
604 (setq n (car en-ret))
609 (mime/encode-header-string n (concat (nth 0 cell) ", "))
611 (setq en-ret (mime/encode-header-string n (nth 0 cell)))
613 (setq dest (concat dest (nth 1 en-ret)))
614 (setq n (car en-ret))
620 ;;; @ utility functions
624 (defun hex-char-to-number (chr)
625 (cond ((and (<= ?0 chr)(<= chr ?9)) (- chr ?0))
626 ((and (<= ?A chr)(<= chr ?F)) (+ (- chr ?A) 10))
627 ((and (<= ?a chr)(<= chr ?f)) (+ (- chr ?a) 10))
630 (defun number-to-hex-char (n)
636 ;;; @ utility for encoder
639 ;;; @@ encoded-word length
642 (defun mime/encoded-word-length (string encoding)
643 (cond ((equal encoding "B") (mime/base64-length string))
644 ((equal encoding "Q") (mime/Quoted-Printable-length string))
648 (defun mime/base64-length (string)
649 (let ((l (length string))
652 (if (= (mod l 3) 0) 0 1)
656 (defun mime/Quoted-Printable-length (string &optional mode)
657 (let ((l 0)(i 0)(len (length string)) chr)
659 (setq chr (elt string i))
660 (if (string-match "[A-Za-z0-9!*+/=_---]" (char-to-string chr))
667 ;;; @@ separate by character set
671 (defconst LC-space 2)
673 ;; by mol. 1993/10/16
674 (defun mime/char-type (chr)
675 (if (or (= chr 32)(= chr ?\t))
680 (defun mime/separate-string-by-chartype (string)
681 (let ((len (length string))
686 (setq chr (elt string 0))
687 (setq pcs (mime/char-type chr))
688 (setq i (char-bytes chr))
689 (setq ds (substring string 0 i))
691 (setq chr (elt string i))
692 (setq cs (mime/char-type chr))
693 (setq j (+ i (char-bytes chr)))
694 (setq s (substring string i j))
697 (setq ds (concat ds s))
698 (progn (setq dest (append dest (list (cons pcs ds))))
703 (if (not (string= ds ""))
704 (setq dest (append dest (list (cons pcs ds)))))
708 (defun mime/separate-string-by-charset (str)
709 (let ((rl (mime/separate-string-by-chartype str))
710 (i 1) len (pcell nil) cell ncell dpcell (dest nil) LC)
711 (setq len (length rl))
712 (setq dpcell (list (nth 0 rl)))
713 (setq cell (nth 1 rl))
714 (setq ncell (nth 2 rl))
716 (setq LC (car (car dpcell)))
717 (cond ((and (not (eq LC lc-ascii))
718 (eq (car cell) LC-space)
719 (not (eq (car ncell) lc-ascii)))
720 (setq dpcell (list (cons LC
721 (concat (cdr (car dpcell)) (cdr cell))
724 ((and (not (eq LC lc-ascii))
726 (setq dpcell (list (cons LC
727 (concat (cdr (car dpcell)) (cdr cell))
730 ((and (eq LC lc-ascii)
731 (member (car cell) mime/latin-lc-list))
732 (setq dpcell (list (cons (car cell)
733 (concat (cdr (car dpcell)) (cdr cell))
736 ((and (member LC mime/latin-lc-list)
737 (eq (car cell) lc-ascii))
738 (setq dpcell (list (cons LC
739 (concat (cdr (car dpcell)) (cdr cell))
743 (setq dest (append dest dpcell))
744 (setq dpcell (list cell))
748 (setq ncell (nth (+ i 1) rl))
750 (setq dest (append dest dpcell))
753 (defun mime/separate-string-for-encoder (string)
755 (if (string-match "[ \t]+$" string)
757 (setq lastspace (substring string
760 (setq string (substring string 0 (match-beginning 0)))
762 (let ((rl (mime/separate-string-by-charset string))
763 (i 0) len cell0 cell1 cell2 (dest nil))
764 (setq len (length rl))
765 (setq cell0 (nth 0 rl))
766 (setq cell1 (nth 1 rl))
767 (setq cell2 (nth 2 rl))
769 (cond ((and (not (eq (car cell0) lc-ascii))
770 (eq (car cell1) LC-space)
771 (not (eq (car cell2) lc-ascii))
776 (cdr (assoc (car cell0)
777 mime/lc-charset-and-encoding-alist))
778 (concat (cdr cell0) (cdr cell1))
781 (setq cell0 (nth i rl))
782 (setq cell1 (nth (+ i 1) rl))
783 (setq cell2 (nth (+ i 2) rl))
789 (cdr (assoc (car cell0)
790 mime/lc-charset-and-encoding-alist))
795 (setq cell2 (nth (+ i 2) rl))
800 (list (cons nil lastspace))))
806 ;;; basic functions for MIME header decoder
809 ;;; @ utility for decoder
812 (defun mime/unfolding ()
813 (goto-char (point-min))
815 (while (re-search-forward message/field-regexp nil t)
816 (setq beg (match-beginning 0))
817 (setq end (match-end 0))
818 (setq field (buffer-substring beg end))
819 (if (string-match mime/encoded-word-regexp field)
823 (narrow-to-region (goto-char beg) end)
824 (while (re-search-forward "\n[ \t]+" nil t)
832 (defun mime/prepare-decode-message-header ()
834 (goto-char (point-min))
835 (while (re-search-forward
836 (concat (regexp-quote "?=")
840 (replace-match "?==?")
844 (run-hooks 'mime/tiny-mime-load-hook)
849 ;;; mode: outline-minor
850 ;;; outline-regexp: ";;; @+\\|(......"