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
12 "$Id: tiny-mime.el,v 4.7 1994/08/03 05:40:35 morioka Exp $")
14 (defconst mime/tiny-mime-version
15 (and (string-match "[0-9][0-9.]*" mime/RCS-ID)
16 (substring mime/RCS-ID (match-beginning 0)(match-end 0))
22 ;;; @ MIME encoded-word definition
25 (defconst mime/charset-regexp "[A-Za-z0-9!#$%&'*+---^_`{}|~]")
26 (defconst mime/encoded-text-regexp "[!->@-~]+")
28 (defconst mime/Base64-token-regexp "[A-Za-z0-9+/=]")
29 (defconst mime/Base64-encoded-text-regexp
31 mime/Base64-token-regexp
32 mime/Base64-token-regexp
33 mime/Base64-token-regexp
34 mime/Base64-token-regexp
36 (defconst mime/Base64-encoding-and-encoded-text-regexp
37 (concat "\\(B\\)\\?" mime/Base64-encoded-text-regexp))
39 (defconst mime/Quoted-Printable-hex-char "[0123456789ABCDEF]")
40 (defconst mime/Quoted-Printable-encoded-text-regexp
41 (concat "\\([^=?_]\\|="
42 mime/Quoted-Printable-hex-char
43 mime/Quoted-Printable-hex-char
45 (defconst mime/Quoted-Printable-encoding-and-encoded-text-regexp
46 (concat "\\(Q\\)\\?" mime/Quoted-Printable-encoded-text-regexp))
48 (defconst mime/encoded-word-regexp (concat (regexp-quote "=?")
56 mime/encoded-text-regexp
60 (defun mime/nth-string (s n)
62 (substring s (match-beginning n) (match-end n))
63 (buffer-substring (match-beginning n) (match-end n))))
65 (defun mime/encoded-word-charset (str)
66 (mime/nth-string str 1))
68 (defun mime/encoded-word-encoding (str)
69 (mime/nth-string str 2))
71 (defun mime/encoded-word-encoded-text (str)
72 (mime/nth-string str 3))
74 (defun mime/rest-of-string (str)
76 (substring str (match-end 0))
77 (buffer-substring (match-end 0))))
82 (defvar mime/no-encoding-header-fields '("X-Nsubject"))
84 (defvar mime/use-X-Nsubject nil)
87 ;;; @ compatible module among Mule, NEmacs and NEpoch
89 (cond ((boundp 'MULE) (require 'tm-mule))
90 ((boundp 'NEMACS)(require 'tm-nemacs))
91 (t (require 'tm-orig))
95 ;;; @ Application Interface
98 ;;; @@ MIME header decoders
102 (defun mime/decode-encoded-word (word)
103 (if (string-match mime/encoded-word-regexp word)
104 (let ((charset (upcase (mime/encoded-word-charset word)))
105 (encoding (mime/encoded-word-encoding word))
106 (text (mime/encoded-word-encoded-text word)))
107 (mime/decode-encoded-text charset encoding text))
110 (defun mime/decode-region (beg end)
114 (narrow-to-region beg end)
115 (goto-char (point-min))
116 (let (charset encoding text)
117 (while (re-search-forward mime/encoded-word-regexp nil t)
118 (insert (mime/decode-encoded-word
120 (buffer-substring (match-beginning 0) (match-end 0))
121 (delete-region (match-beginning 0) (match-end 0))
127 (defun mime/decode-message-header ()
131 (narrow-to-region (goto-char (point-min))
132 (progn (re-search-forward "^$" nil t) (point)))
133 (mime/prepare-decode-message-header)
134 (mime/decode-region (point-min) (point-max))
137 (defun mime/decode-string (str)
138 (let ((dest "")(ew nil)
140 (while (setq beg (string-match mime/encoded-word-regexp str))
142 (if (not (and (eq ew t) (string= (substring str 0 beg) " ")))
143 (setq dest (concat dest (substring str 0 beg)
147 (setq end (match-end 0))
148 (setq dest (concat dest (mime/decode-encoded-word (substring str beg end))
150 (setq str (substring str end))
156 ;;; @@ MIME header encoders
159 (defun mime/encode-string (string encoding &optional mode)
160 (cond ((equal encoding "B") (mime/base64-encode-string string))
161 ((equal encoding "Q") (mime/Quoted-Printable-encode-string string mode))
165 (defun mime/encode-field (str)
166 (setq str (message/unfolding-string str))
167 (let ((ret (message/divide-field str))
168 field-name field-body)
169 (setq field-name (car ret))
170 (setq field-body (nth 1 ret))
171 (if (string= field-body "")
173 (concat field-name " "
174 (if (or (string-match "^Reply-To:$" field-name)
175 (string-match "^From:$" field-name)
176 (string-match "^Sender:$" field-name)
177 (string-match "^Resent-Reply-To:$" field-name)
178 (string-match "^Resent-From:$" field-name)
179 (string-match "^Resent-Sender:$" field-name)
180 (string-match "^To:$" field-name)
181 (string-match "^Resent-To:$" field-name)
182 (string-match "^cc:$" field-name)
183 (string-match "^Resent-cc:$" field-name)
184 (string-match "^bcc:$" field-name)
185 (string-match "^Resent-bcc:$" field-name)
187 (mime/encode-address-list (+ (length field-name) 1)
191 (n (length mime/no-encoding-header-fields))
194 (setq fn (nth i mime/no-encoding-header-fields))
195 (if (string-match (concat "^" fn ":$") field-name)
197 (throw 'label field-body)
201 (nth 1 (mime/encode-header-string (+ (length field-name) 1)
207 (defun mime/encode-message-header ()
211 (narrow-to-region (goto-char (point-min))
214 (concat "^" (regexp-quote mail-header-separator) "$")
218 (goto-char (point-min))
220 (while (re-search-forward "^.+:.*\\(\n\\s +.*\\)*" nil t)
221 (setq beg (match-beginning 0))
222 (setq end (match-end 0))
223 (setq field (buffer-substring beg end))
224 (insert (mime/encode-field
226 (buffer-substring beg end)
227 (delete-region beg end)
230 (if mime/use-X-Nsubject
232 (goto-char (point-min))
233 (if (re-search-forward "^Subject:.*\\(\n\\s +.*\\)*" nil t)
234 (let ((str (buffer-substring (match-beginning 0)(match-end 0))))
235 (if (string-match mime/encoded-word-regexp str)
238 (nth 1 (message/divide-field
240 (message/unfolding-string str))
246 ;;; @ Base64 (B-encode) decoder/encoder
247 ;;; by Enami Tsugutomo
250 (defun mime/base64-decode-string (string)
251 (mime/base64-mapconcat (function mime/base64-decode-chars) 4 string))
253 ;; (mime/base64-encode-string (mime/base64-decode-string "GyRAOjRGI0stGyhK"))
254 (defun mime/base64-encode-string (string &optional mode)
255 (let ((es (mime/base64-mapconcat (function mime/base64-encode-chars) 3 string))
257 (setq m (mod (length es) 4))
264 ;; (char-to-string (mime/base64-bit-to-char 26))
265 (defun mime/base64-bit-to-char (n)
266 (cond ((eq n nil) ?=)
268 ((< n 52) (+ ?a (- n 26)))
269 ((< n 62) (+ ?0 (- n 52)))
272 (t (error "not a base64 integer %d" n))))
274 (defun mime/base64-char-to-bit (c)
275 (cond ((and (<= ?A c) (<= c ?Z)) (- c ?A))
276 ((and (<= ?a c) (<= c ?z)) (+ (- c ?a) 26))
277 ((and (<= ?0 c) (<= c ?9)) (+ (- c ?0) 52))
281 (t (error "not a base64 character %c" c))))
283 (defun mime/mask (i n) (logand i (1- (ash 1 n))))
285 (defun mime/base64-encode-1 (a &optional b &optional c)
287 (cons (logior (ash (mime/mask a 2) (- 6 2))
290 (cons (logior (ash (mime/mask b 4) (- 6 4))
293 (cons (mime/mask c (- 6 0))
296 (defun mime/base64-decode-1 (a b &optional c &optional d)
297 (cons (logior (ash a 2) (ash b (- 2 6)))
298 (if c (cons (logior (ash (mime/mask b 4) 4)
299 (mime/mask (ash c (- 4 6)) 4))
300 (if d (cons (logior (ash (mime/mask c 2) 6) d)
303 ;; (mime/base64-decode-chars ?G ?y ?R ?A)
304 (defun mime/base64-decode-chars (a b c d)
305 (apply (function mime/base64-decode-1)
306 (mapcar (function mime/base64-char-to-bit)
309 ;; (mapcar (function char-to-string) (mime/base64-encode-chars 27 36 64))
310 (defun mime/base64-encode-chars (a b c)
311 (mapcar (function mime/base64-bit-to-char) (mime/base64-encode-1 a b c)))
313 (defun mime/base64-fecth-from (func from pos len)
317 ret (cons (funcall func from (+ pos len)) ret)))
320 (defun mime/base64-fecth-from-buffer (from pos len)
321 (mime/base64-fecth-from (function (lambda (f p) (char-after p)))
324 (defun mime/base64-fecth-from-string (from pos len)
325 (mime/base64-fecth-from (function (lambda (f p)
326 (if (< p (length f)) (aref f p))))
329 (defun mime/base64-fecth (source pos len)
330 (cond ((stringp source) (mime/base64-fecth-from-string source pos len))
331 (t (mime/base64-fecth-from-buffer source pos len))))
333 (defun mime/base64-mapconcat (func unit string)
335 (while (< i (length string))
337 (apply (function concat)
339 (mapcar (function char-to-string)
340 (apply func (mime/base64-fecth string i unit)))))
344 ;;; @ Quoted-Printable (Q-encode) encoder/decoder
347 (defun mime/Quoted-Printable-decode-string (str)
352 (setq chr (elt str i))
356 (setq h (hex-char-to-number (elt str (+ i 1))))
357 (setq l (hex-char-to-number (elt str (+ i 2))))
358 (setq num (+ (* h 16) l))
359 (setq dest (concat dest (char-to-string num)))
363 (setq dest (concat dest (char-to-string chr)))
367 (setq dest (concat dest (char-to-string 32)))
371 (setq dest (concat dest (char-to-string chr)))
377 (defun mime/Quoted-Printable-encode-string (str &optional mode)
384 (setq chr (elt str i))
386 (setq dest (concat dest "_"))
391 (and (eq mode 'comment)
396 (and (eq mode 'phrase)
397 (not (string-match "[A-Za-z0-9!*+/=_---]"
398 (char-to-string chr)))
402 (setq dest (concat dest
404 (char-to-string (number-to-hex-char (/ chr 16)))
405 (char-to-string (number-to-hex-char (% chr 16)))
408 (t (setq dest (concat dest (char-to-string chr)))
414 ;;; @ functions for message header encoding
417 (defun mime/encode-and-split-string (n string charset encoding)
419 (len (length string))
420 (js (mime/convert-string-from-emacs string charset))
421 (cesl (+ (length charset) (length encoding) 6 ))
423 (setq ewl (mime/encoded-word-length js encoding))
426 (setq m (+ n ewl cesl))
429 (while (and (< i len)
430 (setq js (mime/convert-string-from-emacs
431 (substring string 0 i) charset))
432 (setq m (+ n (mime/encoded-word-length js encoding) cesl))
435 (setq i (+ i (char-bytes (elt string i))))
437 (setq js (mime/convert-string-from-emacs
438 (substring string 0 j) charset))
439 (setq m (+ n (mime/encoded-word-length js encoding) cesl))
440 (setq rest (substring string j))
445 (list m (concat "=?" charset "?" encoding "?"
446 (mime/encode-string js encoding)
451 (defun mime/encode-header-word (n string charset encoding)
452 (let (dest str ret m)
453 (if (null (setq ret (mime/encode-and-split-string n string charset encoding)))
456 (setq dest (nth 1 ret))
458 (setq str (nth 2 ret))
459 (while (and (stringp str)
460 (setq ret (mime/encode-and-split-string 1 str charset encoding))
462 (setq dest (concat dest "\n " (nth 1 ret)))
464 (setq str (nth 2 ret))
470 (defun mime/encode-header-string (n string &optional mode)
471 (let ((ssl (mime/separate-string-for-encoder string))
472 i len cell et w ew (dest "") b l)
473 (setq len (length ssl))
474 (setq cell (nth 0 ssl))
479 (if (> (+ n (string-width w)) 76)
481 (setq dest (concat dest "\n "))
485 (setq dest (concat dest w))
486 (setq b (+ b (string-width w)))
489 (setq ew (mime/encode-header-word n (cdr cell) (car et) (cdr et)))
490 (setq dest (nth 1 ew))
495 (setq cell (nth i ssl))
498 (cond ((string-match "^[ \t]*$" w)
499 (setq b (+ b (string-width (cdr cell))))
500 (setq dest (concat dest (cdr cell)))
503 (if (> (+ b (string-width w)) 76)
505 (if (eq (elt dest (- (length dest) 1)) 32)
506 (setq dest (substring dest 0 (- (length dest) 1)))
508 (setq dest (concat dest "\n " w))
509 (setq b (+ (length w) 1))
511 (setq l (length dest))
513 (eq (elt dest (- l 2)) ?\?)
514 (eq (elt dest (- l 1)) ?=)
517 (setq dest (concat dest " "))
520 (setq dest (concat dest w))
521 (setq b (+ b (string-width w)))
524 (if (not (eq (elt dest (- (length dest) 1)) 32))
526 (setq dest (concat dest " "))
529 (setq ew (mime/encode-header-word b (cdr cell) (car et) (cdr et)))
531 (if (string-match "^\n" (nth 1 ew))
532 (setq dest (concat (substring dest 0 (- (length dest) 1))
534 (setq dest (concat dest (nth 1 ew)))
541 (defun mime/encode-address-list (n str)
542 (let ((ret (message/parse-addresses str))
543 len (i 0) cell en-ret j cl (dest "") s)
544 (setq len (length ret))
546 (setq cell (nth i ret))
547 (cond ((string= (nth 1 cell) "<")
548 (setq en-ret (mime/encode-header-string n (nth 0 cell) 'phrase))
549 (setq dest (concat dest (nth 1 en-ret)))
550 (setq n (car en-ret))
553 (mime/encode-header-string
554 n (concat (nth 1 cell)(nth 2 cell)(nth 3 cell) ", ")))
555 (setq en-ret (mime/encode-header-string
556 n (concat (nth 1 cell)(nth 2 cell)(nth 3 cell))))
558 (if (and (eq (elt (nth 1 en-ret) 0) ?\n)
559 (eq (elt dest (- (length dest) 1)) 32))
560 (setq dest (substring dest 0 (- (length dest) 1)))
562 (setq dest (concat dest (nth 1 en-ret)))
563 (setq n (car en-ret))
566 (setq en-ret (mime/encode-header-string n (nth 0 cell)))
567 (setq dest (concat dest (nth 1 en-ret)))
568 (setq n (car en-ret))
570 (setq en-ret (mime/encode-header-string (+ n 2) (nth 2 cell) 'comment))
571 (if (eq (elt (nth 1 en-ret) 0) ?\n)
573 (setq dest (concat dest "\n ("))
574 (setq en-ret (mime/encode-header-string 2 (nth 2 cell) 'comment))
577 (setq dest (concat dest " ("))
579 (setq dest (concat dest (nth 1 en-ret)))
580 (setq n (car en-ret))
583 (mime/encode-header-string n (concat (nth 3 cell) ", ")))
584 (setq en-ret (mime/encode-header-string n (nth 3 cell)))
586 (setq dest (concat dest (nth 1 en-ret)))
587 (setq n (car en-ret))
592 (mime/encode-header-string n (concat (nth 0 cell) ", ")))
593 (setq en-ret (mime/encode-header-string n (nth 0 cell)))
595 (setq dest (concat dest (nth 1 en-ret)))
596 (setq n (car en-ret))
601 ;;; @ utility functions
605 (defun hex-char-to-number (chr)
606 (cond ((and (<= ?0 chr)(<= chr ?9)) (- chr ?0))
607 ((and (<= ?A chr)(<= chr ?F)) (+ (- chr ?A) 10))
608 ((and (<= ?a chr)(<= chr ?f)) (+ (- chr ?a) 10))
611 (defun number-to-hex-char (n)
617 ;;; @ utility for encoder
620 ;;; @@ encoded-word length
623 (defun mime/encoded-word-length (string encoding)
624 (cond ((equal encoding "B") (mime/base64-length string))
625 ((equal encoding "Q") (mime/Quoted-Printable-length string))
629 (defun mime/base64-length (string)
630 (let ((l (length string))
633 (if (= (mod l 3) 0) 0 1)
637 (defun mime/Quoted-Printable-length (string &optional mode)
638 (let ((l 0)(i 0)(len (length string)) chr)
640 (setq chr (elt string i))
641 (if (string-match "[A-Za-z0-9!*+/=_---]" (char-to-string chr))
648 ;;; @@ separate by character set
652 (defconst LC-space 2)
654 ;; by mol. 1993/10/16
655 (defun mime/char-type (chr)
656 (if (or (= chr 32)(= chr ?\t))
658 (mime/char-leading-char chr)
661 (defun mime/separate-string-by-chartype (string)
662 (let ((len (length string))
666 (progn (setq chr (elt string 0))
667 (setq pcs (mime/char-type chr))
668 (setq i (char-bytes chr))
669 (setq ds (substring string 0 i))
671 (setq chr (elt string i))
672 (setq cs (mime/char-type chr))
673 (setq j (+ i (char-bytes chr)))
674 (setq s (substring string i j))
677 (setq ds (concat ds s))
678 (progn (setq dest (append dest (list (cons pcs ds))))
683 (if (not (string= ds ""))
684 (setq dest (append dest (list (cons pcs ds)))))
688 (defun mime/separate-string-by-charset (str)
689 (let ((rl (mime/separate-string-by-chartype str))
690 (i 1) len (pcell nil) cell ncell dpcell (dest nil) LC)
691 (setq len (length rl))
692 (setq dpcell (list (nth 0 rl)))
693 (setq cell (nth 1 rl))
694 (setq ncell (nth 2 rl))
696 (setq LC (car (car dpcell)))
697 (cond ((and (not (eq LC lc-ascii))
698 (eq (car cell) LC-space)
699 (not (eq (car ncell) lc-ascii)))
700 (setq dpcell (list (cons LC
701 (concat (cdr (car dpcell)) (cdr cell))
704 ((and (not (eq LC lc-ascii))
706 (setq dpcell (list (cons LC
707 (concat (cdr (car dpcell)) (cdr cell))
710 ((and (eq LC lc-ascii)
711 (member (car cell) mime/latin-lc-list))
712 (setq dpcell (list (cons (car cell)
713 (concat (cdr (car dpcell)) (cdr cell))
716 ((and (member LC mime/latin-lc-list)
717 (eq (car cell) lc-ascii))
718 (setq dpcell (list (cons LC
719 (concat (cdr (car dpcell)) (cdr cell))
723 (setq dest (append dest dpcell))
724 (setq dpcell (list cell))
728 (setq ncell (nth (+ i 1) rl))
730 (setq dest (append dest dpcell))
733 (defun mime/separate-string-for-encoder (string)
735 (if (string-match "[ \t]+$" string)
737 (setq lastspace (substring string
740 (setq string (substring string 0 (match-beginning 0)))
742 (let ((rl (mime/separate-string-by-charset string))
743 (i 0) len cell0 cell1 cell2 (dest nil))
744 (setq len (length rl))
745 (setq cell0 (nth 0 rl))
746 (setq cell1 (nth 1 rl))
747 (setq cell2 (nth 2 rl))
749 (cond ((and (not (eq (car cell0) lc-ascii))
750 (eq (car cell1) LC-space)
751 (not (eq (car cell2) lc-ascii))
756 (cdr (assoc (car cell0)
757 mime/lc-charset-and-encoding-alist))
758 (concat (cdr cell0) (cdr cell1))
761 (setq cell0 (nth i rl))
762 (setq cell1 (nth (+ i 1) rl))
763 (setq cell2 (nth (+ i 2) rl))
769 (cdr (assoc (car cell0)
770 mime/lc-charset-and-encoding-alist))
775 (setq cell2 (nth (+ i 2) rl))
780 (list (cons nil lastspace))))
786 ;;; basic functions for MIME header decoder
789 ;;; @ utility for decoder
792 (defun mime/unfolding ()
793 (goto-char (point-min))
795 (while (re-search-forward message/field-regexp nil t)
796 (setq beg (match-beginning 0))
797 (setq end (match-end 0))
798 (setq field (buffer-substring beg end))
799 (if (string-match mime/encoded-word-regexp field)
803 (narrow-to-region (goto-char beg) end)
804 (while (re-search-forward "\n[ \t]+" nil t)
812 (defun mime/prepare-decode-message-header ()
814 (goto-char (point-min))
815 (while (re-search-forward
816 (concat (regexp-quote "?=")
820 (replace-match "?==?")
824 (run-hooks 'mime/tiny-mime-load-hook)
829 ;;; mode: outline-minor
830 ;;; outline-regexp: ";;; @+\\|(......"