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
23 "$Id: tiny-mime.el,v 6.2 1995/08/27 19:05:07 morioka Exp $")
25 (defconst mime/tiny-mime-version (get-version-string mime/RCS-ID))
28 ;;; @ MIME encoded-word definition
31 (defconst mime/tspecials "][\000-\040()<>@,\;:\\\"/?.=")
32 (defconst mime/token-regexp (concat "[^" mime/tspecials "]+"))
33 (defconst mime/charset-regexp mime/token-regexp)
34 (defconst mime/encoded-text-regexp "[!->@-~]+")
36 (defconst mime/Base64-token-regexp "[A-Za-z0-9+/=]")
37 (defconst mime/Base64-encoded-text-regexp
39 mime/Base64-token-regexp
40 mime/Base64-token-regexp
41 mime/Base64-token-regexp
42 mime/Base64-token-regexp
44 (defconst mime/Base64-encoding-and-encoded-text-regexp
45 (concat "\\(B\\)\\?" mime/Base64-encoded-text-regexp))
47 (defconst mime/Quoted-Printable-hex-char-regexp "[0123456789ABCDEF]")
48 (defconst mime/Quoted-Printable-octet-regexp
50 mime/Quoted-Printable-hex-char-regexp
51 mime/Quoted-Printable-hex-char-regexp))
52 (defconst mime/Quoted-Printable-encoded-text-regexp
53 (concat "\\([^=?]\\|" mime/Quoted-Printable-octet-regexp "\\)+"))
54 (defconst mime/Quoted-Printable-encoding-and-encoded-text-regexp
55 (concat "\\(Q\\)\\?" mime/Quoted-Printable-encoded-text-regexp))
57 (defconst mime/encoded-word-regexp (concat (regexp-quote "=?")
65 mime/encoded-text-regexp
69 (defun mime/nth-string (s n)
71 (substring s (match-beginning n) (match-end n))
72 (buffer-substring (match-beginning n) (match-end n))))
74 (defun mime/encoded-word-charset (str)
75 (mime/nth-string str 1))
77 (defun mime/encoded-word-encoding (str)
78 (mime/nth-string str 2))
80 (defun mime/encoded-word-encoded-text (str)
81 (mime/nth-string str 3))
83 (defun mime/rest-of-string (str)
85 (substring str (match-end 0))
86 (buffer-substring (match-end 0)(point-max))
93 (defvar mime/no-encoding-header-fields '("X-Nsubject" "Newsgroups"))
95 (defvar mime/use-X-Nsubject nil)
98 ;;; @ compatible module among Mule, NEmacs and NEpoch
101 (cond ((boundp 'MULE) (require 'tm-mule))
102 ((boundp 'NEMACS)(require 'tm-nemacs))
103 (t (require 'tm-orig))
107 ;;; @ Application Interface
110 ;;; @@ MIME header decoders
113 (defun mime/decode-encoded-text (charset encoding str)
115 (cond ((string= "B" encoding)
116 (base64-decode-string str))
117 ((string= "Q" encoding)
118 (q-encoding-decode-string str))
119 (t (message "unknown encoding %s" encoding)
122 (mime/convert-string-to-emacs charset dest)
125 (defun mime/decode-encoded-word (word)
126 (or (if (string-match mime/encoded-word-regexp word)
127 (let ((charset (upcase (mime/encoded-word-charset word)))
128 (encoding (upcase (mime/encoded-word-encoding word)))
129 (text (mime/encoded-word-encoded-text word)))
130 (mime/decode-encoded-text charset encoding text)
134 (defun mime/decode-region (beg end)
138 (narrow-to-region beg end)
139 (goto-char (point-min))
140 (let (charset encoding text)
141 (while (re-search-forward mime/encoded-word-regexp nil t)
142 (insert (mime/decode-encoded-word
144 (buffer-substring (match-beginning 0) (match-end 0))
145 (delete-region (match-beginning 0) (match-end 0))
151 (defun mime/decode-message-header ()
155 (narrow-to-region (goto-char (point-min))
156 (progn (re-search-forward "^$" nil t) (point)))
157 (mime/prepare-decode-message-header)
158 (mime/decode-region (point-min) (point-max))
161 (defun mime/decode-string (str)
162 (let ((dest "")(ew nil)
164 (while (setq beg (string-match mime/encoded-word-regexp str))
166 (if (not (and (eq ew t) (string= (substring str 0 beg) " ")))
167 (setq dest (concat dest (substring str 0 beg)
171 (setq end (match-end 0))
172 (setq dest (concat dest
173 (mime/decode-encoded-word (substring str beg end))
175 (setq str (substring str end))
181 ;;; @@ MIME header encoders
184 (defun mime/encode-string (string encoding &optional mode)
185 (cond ((string= encoding "B") (base64-encode-string string))
186 ((string= encoding "Q") (q-encoding-encode-string string mode))
189 (defun mime/encode-field (str)
190 (setq str (message/unfolding-string str))
191 (let ((ret (message/divide-field str))
192 field-name field-body)
193 (setq field-name (car ret))
194 (setq field-body (nth 1 ret))
195 (concat field-name " "
196 (cond ((string= field-body "") "")
197 ((or (string-match "^Reply-To:$" field-name)
198 (string-match "^From:$" field-name)
199 (string-match "^Sender:$" field-name)
200 (string-match "^Resent-Reply-To:$" field-name)
201 (string-match "^Resent-From:$" field-name)
202 (string-match "^Resent-Sender:$" field-name)
203 (string-match "^To:$" field-name)
204 (string-match "^Resent-To:$" field-name)
205 (string-match "^cc:$" field-name)
206 (string-match "^Resent-cc:$" field-name)
207 (string-match "^bcc:$" field-name)
208 (string-match "^Resent-bcc:$" field-name)
210 (mime/encode-address-list
211 (+ (length field-name) 1) field-body)
215 (let ((r mime/no-encoding-header-fields) fn)
218 (if (string-match (concat "^" fn ":$") field-name)
219 (throw 'tag field-body)
223 (nth 1 (mime/encode-header-string
224 (+ (length field-name) 1) field-body))
229 (defun mime/encode-message-header ()
233 (narrow-to-region (goto-char (point-min))
237 "^" (regexp-quote mail-header-separator) "$")
241 (goto-char (point-min))
243 (while (re-search-forward "^.+:.*\\(\n\\s +.*\\)*" nil t)
244 (setq beg (match-beginning 0))
245 (setq end (match-end 0))
246 (setq field (buffer-substring beg end))
247 (insert (mime/encode-field
249 (buffer-substring beg end)
250 (delete-region beg end)
253 (if mime/use-X-Nsubject
255 (goto-char (point-min))
256 (if (re-search-forward "^Subject:.*\\(\n\\s +.*\\)*" nil t)
257 (let ((str (buffer-substring (match-beginning 0)
259 (if (string-match mime/encoded-word-regexp str)
262 (nth 1 (message/divide-field
264 (message/unfolding-string str))
271 ;;; @ functions for message header encoding
274 (defun mime/encode-and-split-string (n string charset encoding)
276 (len (length string))
277 (js (mime/convert-string-from-emacs string charset))
278 (cesl (+ (length charset) (length encoding) 6 ))
280 (setq ewl (mime/encoded-word-length js encoding))
283 (setq m (+ n ewl cesl))
286 (while (and (< i len)
287 (setq js (mime/convert-string-from-emacs
288 (substring string 0 i) charset))
290 (mime/encoded-word-length js encoding)
294 (setq i (+ i (char-bytes (elt string i))))
296 (setq js (mime/convert-string-from-emacs
297 (substring string 0 j) charset))
298 (setq m (+ n (mime/encoded-word-length js encoding) cesl))
299 (setq rest (substring string j))
304 (list m (concat "=?" charset "?" encoding "?"
305 (mime/encode-string js encoding)
310 (defun mime/encode-header-word (n string charset encoding)
311 (let (dest str ret m)
313 (mime/encode-and-split-string n string charset encoding)))
316 (setq dest (nth 1 ret))
318 (setq str (nth 2 ret))
319 (while (and (stringp str)
321 (mime/encode-and-split-string
322 1 str charset encoding))
324 (setq dest (concat dest "\n " (nth 1 ret)))
326 (setq str (nth 2 ret))
332 (defun mime/encode-header-string (n string &optional mode)
333 (if (string= string "")
335 (let ((ssl (mime/separate-string-for-encoder string))
336 i len cell et w ew (dest "") b l)
337 (setq len (length ssl))
338 (setq cell (nth 0 ssl))
340 ;; string-width crashes when the argument is nil,
341 ;; so replace the argument
342 ;; (original modification by Kenji Rikitake 9-JAN-1995)
343 (setq w (or (cdr cell) ""))
346 (if (> (+ n (string-width w)) 76)
348 (setq dest (concat dest "\n "))
352 (setq dest (concat dest w))
353 (setq b (+ b (string-width w)))
356 (setq ew (mime/encode-header-word n (cdr cell) (car et) (cdr et)))
357 (setq dest (nth 1 ew))
362 (setq cell (nth i ssl))
365 (cond ((string-match "^[ \t]*$" w)
366 (setq b (+ b (string-width (cdr cell))))
367 (setq dest (concat dest (cdr cell)))
370 (if (> (+ b (string-width w)) 76)
372 (if (eq (elt dest (- (length dest) 1)) 32)
373 (setq dest (substring dest 0 (- (length dest) 1)))
375 (setq dest (concat dest "\n " w))
376 (setq b (+ (length w) 1))
378 (setq l (length dest))
380 (eq (elt dest (- l 2)) ?\?)
381 (eq (elt dest (- l 1)) ?=)
384 (setq dest (concat dest " "))
387 (setq dest (concat dest w))
388 (setq b (+ b (string-width w)))
391 (if (not (eq (elt dest (- (length dest) 1)) 32))
393 (setq dest (concat dest " "))
397 (mime/encode-header-word b (cdr cell) (car et) (cdr et)))
399 (if (string-match "^\n" (nth 1 ew))
400 (setq dest (concat (substring dest 0 (- (length dest) 1))
402 (setq dest (concat dest (nth 1 ew)))
410 (defun mime/encode-address-list (n str)
411 (let* ((ret (message/parse-addresses str))
412 (r ret) cell en-ret j cl (dest "") s)
415 (cond ((string= (nth 1 cell) "<")
416 (setq en-ret (mime/encode-header-string n (nth 0 cell) 'phrase))
417 (setq dest (concat dest (nth 1 en-ret)))
418 (setq n (car en-ret))
421 (mime/encode-header-string
422 n (concat (nth 1 cell)(nth 2 cell)(nth 3 cell) ", ")))
423 (setq en-ret (mime/encode-header-string
424 n (concat (nth 1 cell)(nth 2 cell)(nth 3 cell))))
426 (if (and (eq (elt (nth 1 en-ret) 0) ?\n)
427 (eq (elt dest (- (length dest) 1)) 32))
428 (setq dest (substring dest 0 (- (length dest) 1)))
430 (setq dest (concat dest (nth 1 en-ret)))
431 (setq n (car en-ret))
434 (setq en-ret (mime/encode-header-string n (nth 0 cell)))
435 (setq dest (concat dest (nth 1 en-ret)))
436 (setq n (car en-ret))
438 (setq en-ret (mime/encode-header-string (+ n 2) (nth 2 cell)
440 (if (eq (elt (nth 1 en-ret) 0) ?\n)
442 (setq dest (concat dest "\n ("))
443 (setq en-ret (mime/encode-header-string 2 (nth 2 cell)
447 (setq dest (concat dest " ("))
449 (setq dest (concat dest (nth 1 en-ret)))
450 (setq n (car en-ret))
453 (mime/encode-header-string n (concat (nth 3 cell) ", "))
455 (setq en-ret (mime/encode-header-string n (nth 3 cell)))
457 (setq dest (concat dest (nth 1 en-ret)))
458 (setq n (car en-ret))
463 (mime/encode-header-string n (concat (nth 0 cell) ", "))
465 (setq en-ret (mime/encode-header-string n (nth 0 cell)))
467 (setq dest (concat dest (nth 1 en-ret)))
468 (setq n (car en-ret))
475 ;;; @ utility for encoder
478 ;;; @@ encoded-word length
481 (defun mime/encoded-word-length (string encoding)
482 (cond ((equal encoding "B") (mime/base64-length string))
483 ((equal encoding "Q") (mime/Quoted-Printable-length string))
487 (defun mime/base64-length (string)
488 (let ((l (length string))
491 (if (= (mod l 3) 0) 0 1)
495 (defun mime/Quoted-Printable-length (string &optional mode)
496 (let ((l 0)(i 0)(len (length string)) chr)
498 (setq chr (elt string i))
499 (if (string-match "[A-Za-z0-9!*+/=_---]" (char-to-string chr))
506 ;;; @@ separate by character set
510 (defconst LC-space 2)
512 ;; by mol. 1993/10/16
513 (defun mime/char-type (chr)
514 (if (or (= chr 32)(= chr ?\t))
519 (defun mime/separate-string-by-chartype (string)
520 (let ((len (length string))
525 (setq chr (elt string 0))
526 (setq pcs (mime/char-type chr))
527 (setq i (char-bytes chr))
528 (setq ds (substring string 0 i))
530 (setq chr (elt string i))
531 (setq cs (mime/char-type chr))
532 (setq j (+ i (char-bytes chr)))
533 (setq s (substring string i j))
536 (setq ds (concat ds s))
537 (progn (setq dest (append dest (list (cons pcs ds))))
542 (if (not (string= ds ""))
543 (setq dest (append dest (list (cons pcs ds)))))
547 (defun mime/separate-string-by-charset (str)
548 (let ((rl (mime/separate-string-by-chartype str))
549 (i 1) len (pcell nil) cell ncell dpcell (dest nil) LC)
550 (setq len (length rl))
551 (setq dpcell (list (nth 0 rl)))
552 (setq cell (nth 1 rl))
553 (setq ncell (nth 2 rl))
555 (setq LC (car (car dpcell)))
556 (cond ((and (not (eq LC lc-ascii))
557 (eq (car cell) LC-space)
558 (not (eq (car ncell) lc-ascii)))
559 (setq dpcell (list (cons LC
560 (concat (cdr (car dpcell)) (cdr cell))
563 ((and (not (eq LC lc-ascii))
565 (setq dpcell (list (cons LC
566 (concat (cdr (car dpcell)) (cdr cell))
569 ((and (eq LC lc-ascii)
570 (member (car cell) mime/latin-lc-list))
571 (setq dpcell (list (cons (car cell)
572 (concat (cdr (car dpcell)) (cdr cell))
575 ((and (member LC mime/latin-lc-list)
576 (eq (car cell) lc-ascii))
577 (setq dpcell (list (cons LC
578 (concat (cdr (car dpcell)) (cdr cell))
582 (setq dest (append dest dpcell))
583 (setq dpcell (list cell))
587 (setq ncell (nth (+ i 1) rl))
589 (setq dest (append dest dpcell))
592 (defun mime/separate-string-for-encoder (string)
594 (if (string-match "[ \t]+$" string)
596 (setq lastspace (substring string
599 (setq string (substring string 0 (match-beginning 0)))
601 (let ((rl (mime/separate-string-by-charset string))
602 (i 0) len cell0 cell1 cell2 (dest nil))
603 (setq len (length rl))
604 (setq cell0 (nth 0 rl))
605 (setq cell1 (nth 1 rl))
606 (setq cell2 (nth 2 rl))
608 (cond ((and (not (eq (car cell0) lc-ascii))
609 (eq (car cell1) LC-space)
610 (not (eq (car cell2) lc-ascii))
615 (cdr (assoc (car cell0)
616 mime/lc-charset-and-encoding-alist))
617 (concat (cdr cell0) (cdr cell1))
620 (setq cell0 (nth i rl))
621 (setq cell1 (nth (+ i 1) rl))
622 (setq cell2 (nth (+ i 2) rl))
628 (cdr (assoc (car cell0)
629 mime/lc-charset-and-encoding-alist))
634 (setq cell2 (nth (+ i 2) rl))
639 (list (cons nil lastspace))))
645 ;;; basic functions for MIME header decoder
648 ;;; @ utility for decoder
651 (defun mime/unfolding ()
652 (goto-char (point-min))
654 (while (re-search-forward message/field-name-regexp nil t)
655 (setq beg (match-beginning 0))
657 (if (re-search-forward "\n[!-9;-~]+:" nil t)
658 (goto-char (match-beginning 0))
659 (if (re-search-forward "^$" nil t)
660 (goto-char (1- (match-beginning 0)))
665 (setq field (buffer-substring beg end))
666 (if (string-match mime/encoded-word-regexp field)
668 (narrow-to-region (goto-char beg) end)
669 (while (re-search-forward "\n[ \t]+" nil t)
672 (goto-char (point-max))
676 (defun mime/prepare-decode-message-header ()
678 (goto-char (point-min))
679 (while (re-search-forward
680 (concat (regexp-quote "?=")
684 (replace-match "?==?")
688 (run-hooks 'mime/tiny-mime-load-hook)
695 ;;; mode: outline-minor
696 ;;; outline-regexp: ";;; @+\\|(......"