tm 6.74.
[elisp/tm.git] / tiny-mime.el
1 ;;;
2 ;;; A multilingual MIME message header encoder/decoder.
3 ;;;     by Morioka Tomohiko (morioka@jaist.ac.jp)
4 ;;;
5 ;;; original MIME decoder is
6 ;;;     mime.el,v 1.5 1992/07/18 07:52:08 by Enami Tsugutomo
7 ;;;
8
9
10 ;;; @ require modules
11 ;;;
12
13 (require 'emu)
14 (require 'mel)
15 (require 'tl-header)
16 (require 'tl-str)
17
18
19 ;;; @ version
20 ;;;
21
22 (defconst mime/RCS-ID
23   "$Id: tiny-mime.el,v 6.2 1995/08/27 19:05:07 morioka Exp $")
24
25 (defconst mime/tiny-mime-version (get-version-string mime/RCS-ID))
26
27
28 ;;; @ MIME encoded-word definition
29 ;;;
30
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 "[!->@-~]+")
35
36 (defconst mime/Base64-token-regexp "[A-Za-z0-9+/=]")
37 (defconst mime/Base64-encoded-text-regexp
38   (concat "\\("
39               mime/Base64-token-regexp
40               mime/Base64-token-regexp
41               mime/Base64-token-regexp
42               mime/Base64-token-regexp
43               "\\)+"))
44 (defconst mime/Base64-encoding-and-encoded-text-regexp
45   (concat "\\(B\\)\\?" mime/Base64-encoded-text-regexp))
46
47 (defconst mime/Quoted-Printable-hex-char-regexp "[0123456789ABCDEF]")
48 (defconst mime/Quoted-Printable-octet-regexp
49   (concat "="
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))
56
57 (defconst mime/encoded-word-regexp (concat (regexp-quote "=?")
58                                            "\\("
59                                            mime/charset-regexp
60                                            "\\)"
61                                            (regexp-quote "?")
62                                            "\\(B\\|Q\\)"
63                                            (regexp-quote "?")
64                                            "\\("
65                                            mime/encoded-text-regexp
66                                            "\\)"
67                                            (regexp-quote "?=")))
68
69 (defun mime/nth-string (s n)
70   (if (stringp s)
71       (substring s (match-beginning n) (match-end n))
72     (buffer-substring (match-beginning n) (match-end n))))
73
74 (defun mime/encoded-word-charset (str)
75   (mime/nth-string str 1))
76
77 (defun mime/encoded-word-encoding (str)
78   (mime/nth-string str 2))
79
80 (defun mime/encoded-word-encoded-text (str)
81   (mime/nth-string str 3))
82
83 (defun mime/rest-of-string (str)
84   (if (stringp str)
85       (substring str (match-end 0))
86     (buffer-substring (match-end 0)(point-max))
87     ))
88
89
90 ;;; @ variables
91 ;;;
92
93 (defvar mime/no-encoding-header-fields '("X-Nsubject" "Newsgroups"))
94
95 (defvar mime/use-X-Nsubject nil)
96
97
98 ;;; @ compatible module among Mule, NEmacs and NEpoch 
99 ;;;
100
101 (cond ((boundp 'MULE)  (require 'tm-mule))
102       ((boundp 'NEMACS)(require 'tm-nemacs))
103       (t               (require 'tm-orig))
104       )
105
106
107 ;;; @ Application Interface
108 ;;;
109
110 ;;; @@ MIME header decoders
111 ;;;
112
113 (defun mime/decode-encoded-text (charset encoding str)
114   (let ((dest
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)
120                   nil))))
121     (if dest
122         (mime/convert-string-to-emacs charset dest)
123       )))
124
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)
131             ))
132       word))
133
134 (defun mime/decode-region (beg end)
135   (interactive "*r")
136   (save-excursion
137     (save-restriction
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 
143                    (prog1
144                        (buffer-substring (match-beginning 0) (match-end 0))
145                      (delete-region (match-beginning 0) (match-end 0))
146                      )
147                   ))
148           ))
149       )))
150
151 (defun mime/decode-message-header ()
152   (interactive "*")
153   (save-excursion
154     (save-restriction
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))
159       )))
160
161 (defun mime/decode-string (str)
162   (let ((dest "")(ew nil)
163         beg end)
164     (while (setq beg (string-match mime/encoded-word-regexp str))
165       (if (> beg 0)
166           (if (not (and (eq ew t) (string= (substring str 0 beg) " ")))
167               (setq dest (concat dest (substring str 0 beg)
168                                  ))
169             )
170         )
171       (setq end (match-end 0))
172       (setq dest (concat dest
173                          (mime/decode-encoded-word (substring str beg end))
174                          ))
175       (setq str (substring str end))
176       (setq ew t)
177       )
178     (concat dest str)
179     ))
180
181 ;;; @@ MIME header encoders
182 ;;;
183
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))
187         ))
188
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)
209                        )
210                    (mime/encode-address-list
211                     (+ (length field-name) 1) field-body)
212                    )
213                   (t
214                    (catch 'tag
215                      (let ((r mime/no-encoding-header-fields) fn)
216                        (while r
217                          (setq fn (car r))
218                          (if (string-match (concat "^" fn ":$") field-name)
219                              (throw 'tag field-body)
220                            )
221                          (setq r (cdr r))
222                          ))
223                      (nth 1 (mime/encode-header-string
224                              (+ (length field-name) 1) field-body))
225                      ))
226                   ))
227     ))
228
229 (defun mime/encode-message-header ()
230   (interactive "*")
231   (save-excursion
232     (save-restriction
233       (narrow-to-region (goto-char (point-min))
234                         (progn
235                           (re-search-forward
236                            (concat
237                             "^" (regexp-quote mail-header-separator) "$")
238                            nil t)
239                           (match-beginning 0)
240                           ))
241       (goto-char (point-min))
242       (let (beg end field)
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
248                    (prog1
249                        (buffer-substring beg end)
250                      (delete-region beg end)
251                      )))
252           ))
253       (if mime/use-X-Nsubject
254           (progn
255             (goto-char (point-min))
256             (if (re-search-forward "^Subject:.*\\(\n\\s +.*\\)*" nil t)
257                 (let ((str (buffer-substring (match-beginning 0)
258                                              (match-end 0))))
259                   (if (string-match mime/encoded-word-regexp str)
260                       (insert (concat
261                                "\nX-Nsubject: "
262                                (nth 1 (message/divide-field
263                                        (mime/decode-string
264                                         (message/unfolding-string str))
265                                        ))))
266                     ))
267               )))
268       )))
269
270
271 ;;; @ functions for message header encoding
272 ;;;
273
274 (defun mime/encode-and-split-string (n string charset encoding)
275   (let ((i 0) (j 0)
276         (len (length string))
277         (js (mime/convert-string-from-emacs string charset))
278         (cesl (+ (length charset) (length encoding) 6 ))
279         ewl m rest)
280     (setq ewl (mime/encoded-word-length js encoding))
281     (if (null ewl) nil
282       (progn
283         (setq m (+ n ewl cesl))
284         (if (> m 76)
285             (progn
286               (while (and (< i len)
287                           (setq js (mime/convert-string-from-emacs
288                                     (substring string 0 i) charset))
289                           (setq m (+ n
290                                      (mime/encoded-word-length js encoding)
291                                      cesl))
292                           (< m 76))
293                 (setq j i)
294                 (setq i (+ i (char-bytes (elt string i))))
295                 )
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))
300               )
301           (setq rest nil))
302         (if (string= js "")
303             (list 1 "" string)
304           (list m (concat "=?" charset "?" encoding "?"
305                           (mime/encode-string js encoding)
306                           "?=") rest))
307         ))
308     ))
309
310 (defun mime/encode-header-word (n string charset encoding)
311   (let (dest str ret m)
312     (if (null (setq ret
313                     (mime/encode-and-split-string n string charset encoding)))
314         nil
315       (progn
316         (setq dest (nth 1 ret))
317         (setq m (car ret))
318         (setq str (nth 2 ret))
319         (while (and (stringp str)
320                     (setq ret
321                           (mime/encode-and-split-string
322                            1 str charset encoding))
323                     )
324           (setq dest (concat dest "\n " (nth 1 ret)))
325           (setq m (car ret))
326           (setq str (nth 2 ret))
327           )
328         (list m dest)
329         ))
330     ))
331
332 (defun mime/encode-header-string (n string &optional mode)
333   (if (string= string "")
334       (list n "")
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))
339       (setq et (car cell))
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) ""))
344       (if (eq et nil)
345           (progn
346             (if (> (+ n (string-width w)) 76)
347                 (progn
348                   (setq dest (concat dest "\n "))
349                   (setq b 1)
350                   )
351               (setq b n))
352             (setq dest (concat dest w))
353             (setq b (+ b (string-width w)))
354             )
355         (progn
356           (setq ew (mime/encode-header-word n (cdr cell) (car et) (cdr et)))
357           (setq dest (nth 1 ew))
358           (setq b (car ew))
359           ))
360       (setq i 1)
361       (while (< i len)
362         (setq cell (nth i ssl))
363         (setq et (car cell))
364         (setq w (cdr cell))
365         (cond ((string-match "^[ \t]*$" w)
366                (setq b (+ b (string-width (cdr cell))))
367                (setq dest (concat dest (cdr cell)))
368                )
369               ((eq et nil)
370                (if (> (+ b (string-width w)) 76)
371                    (progn
372                      (if (eq (elt dest (- (length dest) 1)) 32)
373                          (setq dest (substring dest 0 (- (length dest) 1)))
374                        )
375                      (setq dest (concat dest "\n " w))
376                      (setq b (+ (length w) 1))
377                      )
378                  (setq l (length dest))
379                  (if (and (>= l 2)
380                           (eq (elt dest (- l 2)) ?\?)
381                           (eq (elt dest (- l 1)) ?=)
382                           )
383                      (progn
384                        (setq dest (concat dest " "))
385                        (setq b (+ b 1))
386                        ))
387                  (setq dest (concat dest w))
388                  (setq b (+ b (string-width w)))
389                  ))
390               (t
391                (if (not (eq (elt dest (- (length dest) 1)) 32))
392                    (progn
393                      (setq dest (concat dest " "))
394                      (setq b (+ b 1))
395                      ))
396                (setq ew
397                      (mime/encode-header-word b (cdr cell) (car et) (cdr et)))
398                (setq b (car ew)) 
399                (if (string-match "^\n" (nth 1 ew))
400                    (setq dest (concat (substring dest 0 (- (length dest) 1))
401                                       (nth 1 ew)))
402                  (setq dest (concat dest (nth 1 ew)))
403                  )
404                ))
405         (setq i (+ i 1))
406         )
407       (list b dest)
408       )))
409
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)
413     (while r
414       (setq cell (car r))
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))
419              (if (> (length r) 1)
420                  (setq 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))))
425                )
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)))
429                )
430              (setq dest (concat dest (nth 1 en-ret)))
431              (setq n (car en-ret))
432              )
433             ((= (length cell) 4)
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))
437              
438              (setq en-ret (mime/encode-header-string (+ n 2) (nth 2 cell)
439                                                      'comment))
440              (if (eq (elt (nth 1 en-ret) 0) ?\n)
441                  (progn
442                    (setq dest (concat dest "\n ("))
443                    (setq en-ret (mime/encode-header-string 2 (nth 2 cell)
444                                                            'comment))
445                    )
446                (progn
447                  (setq dest (concat dest " ("))
448                  ))
449              (setq dest (concat dest (nth 1 en-ret)))
450              (setq n (car en-ret))
451              (if (> (length r) 1)
452                  (setq en-ret
453                        (mime/encode-header-string n (concat (nth 3 cell) ", "))
454                        )
455                (setq en-ret (mime/encode-header-string n (nth 3 cell)))
456                )
457              (setq dest (concat dest (nth 1 en-ret)))
458              (setq n (car en-ret))
459              )
460             (t
461              (if (> (length r) 1)
462                  (setq en-ret
463                        (mime/encode-header-string n (concat (nth 0 cell) ", "))
464                        )
465                (setq en-ret (mime/encode-header-string n (nth 0 cell)))
466                )
467              (setq dest (concat dest (nth 1 en-ret)))
468              (setq n (car en-ret))
469              ))
470       (setq r (cdr r))
471       )
472     dest))
473
474
475 ;;; @ utility for encoder
476 ;;;
477
478 ;;; @@ encoded-word length
479 ;;;
480
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))
484         (t nil)
485         ))
486
487 (defun mime/base64-length (string)
488   (let ((l (length string))
489         )
490     (* (+ (/ l 3)
491           (if (= (mod l 3) 0) 0 1)
492           ) 4)
493     ))
494
495 (defun mime/Quoted-Printable-length (string &optional mode)
496   (let ((l 0)(i 0)(len (length string)) chr)
497     (while (< i len)
498       (setq chr (elt string i))
499       (if (string-match "[A-Za-z0-9!*+/=_---]" (char-to-string chr))
500           (setq l (+ l 1))
501         (setq l (+ l 3))
502         )
503       (setq i (+ i 1)) )
504     l))
505
506 ;;; @@ separate by character set
507 ;;;
508
509 ;; by mol. 1993/11/2
510 (defconst LC-space 2)
511
512 ;; by mol. 1993/10/16
513 (defun mime/char-type (chr)
514   (if (or (= chr 32)(= chr ?\t))
515       LC-space
516     (get-lc chr)
517     ))
518
519 (defun mime/separate-string-by-chartype (string)
520   (let ((len (length string))
521         (dest nil) (ds "") s
522         pcs i j cs chr)
523     (if (= len 0) nil
524       (progn
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))
529         (while (< i len)
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))
534           (setq i j)
535           (if (= cs pcs)
536               (setq ds (concat ds s))
537             (progn (setq dest (append dest (list (cons pcs ds))))
538                    (setq pcs cs)
539                    (setq ds s)
540                    ))
541           )
542         (if (not (string= ds ""))
543             (setq dest (append dest (list (cons pcs ds)))))
544         dest)
545       )))
546
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))
554     (while (< i len)
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))
561                                       )))
562              )
563             ((and (not (eq LC lc-ascii))
564                   (eq LC (car cell)))
565              (setq dpcell (list (cons LC
566                                       (concat (cdr (car dpcell)) (cdr cell))
567                                       )))
568              )
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))
573                                       )))
574              )
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))
579                                       )))
580              )
581             (t
582              (setq dest (append dest dpcell))
583              (setq dpcell (list cell))
584              ))
585       (setq i (+ i 1))
586       (setq cell ncell)
587       (setq ncell (nth (+ i 1) rl))
588       )
589     (setq dest (append dest dpcell))
590     ))
591
592 (defun mime/separate-string-for-encoder (string)
593   (let (lastspace)
594     (if (string-match "[ \t]+$" string)
595         (progn
596           (setq lastspace (substring string
597                                      (match-beginning 0)
598                                      (match-end 0)))
599           (setq string (substring string 0 (match-beginning 0)))
600           ))
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))
607       (while (< i len)
608         (cond ((and (not (eq (car cell0) lc-ascii))
609                     (eq (car cell1) LC-space)
610                     (not (eq (car cell2) lc-ascii))
611                     )
612                (setq dest
613                      (append dest (list
614                                    (cons
615                                     (cdr (assoc (car cell0)
616                                                 mime/lc-charset-and-encoding-alist))
617                                     (concat (cdr cell0) (cdr cell1))
618                                     ))))
619                (setq i (+ i 2))
620                (setq cell0 (nth i rl))
621                (setq cell1 (nth (+ i 1) rl))
622                (setq cell2 (nth (+ i 2) rl))
623                )
624               (t
625                (setq dest
626                      (append dest (list
627                                    (cons
628                                     (cdr (assoc (car cell0)
629                                                 mime/lc-charset-and-encoding-alist))
630                                     (cdr cell0)))))
631                (setq i (+ i 1))
632                (setq cell0 cell1)
633                (setq cell1 cell2)
634                (setq cell2 (nth (+ i 2) rl))
635                ))
636         )
637       (append dest
638               (if lastspace
639                   (list (cons nil lastspace))))
640       )))
641               
642               
643
644 ;;;
645 ;;; basic functions for MIME header decoder
646 ;;;
647
648 ;;; @ utility for decoder
649 ;;;
650
651 (defun mime/unfolding ()
652   (goto-char (point-min))
653   (let (field beg end)
654     (while (re-search-forward message/field-name-regexp nil t)
655       (setq beg (match-beginning 0))
656       (setq end (progn
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)))
661                       (end-of-line)
662                       ))
663                   (point)
664                   ))
665       (setq field (buffer-substring beg end))
666       (if (string-match mime/encoded-word-regexp field)
667           (save-restriction
668             (narrow-to-region (goto-char beg) end)
669             (while (re-search-forward "\n[ \t]+" nil t)
670               (replace-match " ")
671               )
672             (goto-char (point-max))
673             ))
674       )))
675
676 (defun mime/prepare-decode-message-header ()
677   (mime/unfolding)
678   (goto-char (point-min))
679   (while (re-search-forward
680           (concat (regexp-quote "?=")
681                   "\\s +"
682                   (regexp-quote "=?"))
683           nil t)
684     (replace-match "?==?")
685     )
686   )
687
688 (run-hooks 'mime/tiny-mime-load-hook)
689
690 (provide 'tiny-mime)
691
692 ;;; @
693 ;;; Local Variables:
694 ;;; mode: emacs-lisp
695 ;;; mode: outline-minor
696 ;;; outline-regexp: ";;; @+\\|(......"
697 ;;; End: