tm 6.77.
[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 (require 'tm-def)
18
19
20 ;;; @ version
21 ;;;
22
23 (defconst mime/RCS-ID
24   "$Id: tiny-mime.el,v 6.4 1995/09/04 01:16:45 morioka Exp $")
25
26 (defconst mime/tiny-mime-version (get-version-string mime/RCS-ID))
27
28
29 ;;; @ MIME encoded-word definition
30 ;;;
31
32 (defconst mime/Base64-token-regexp "[A-Za-z0-9+/=]")
33 (defconst mime/Base64-encoded-text-regexp
34   (concat "\\("
35               mime/Base64-token-regexp
36               mime/Base64-token-regexp
37               mime/Base64-token-regexp
38               mime/Base64-token-regexp
39               "\\)+"))
40 (defconst mime/Base64-encoding-and-encoded-text-regexp
41   (concat "\\(B\\)\\?" mime/Base64-encoded-text-regexp))
42
43 (defconst mime/Quoted-Printable-hex-char-regexp "[0123456789ABCDEF]")
44 (defconst mime/Quoted-Printable-octet-regexp
45   (concat "="
46           mime/Quoted-Printable-hex-char-regexp
47           mime/Quoted-Printable-hex-char-regexp))
48 (defconst mime/Quoted-Printable-encoded-text-regexp
49   (concat "\\([^=?]\\|" mime/Quoted-Printable-octet-regexp "\\)+"))
50 (defconst mime/Quoted-Printable-encoding-and-encoded-text-regexp
51   (concat "\\(Q\\)\\?" mime/Quoted-Printable-encoded-text-regexp))
52
53 (defconst mime/encoded-text-regexp "[!->@-~]+")
54 (defconst mime/encoded-word-regexp (concat (regexp-quote "=?")
55                                            "\\("
56                                            mime/charset-regexp
57                                            "\\)"
58                                            (regexp-quote "?")
59                                            "\\(B\\|Q\\)"
60                                            (regexp-quote "?")
61                                            "\\("
62                                            mime/encoded-text-regexp
63                                            "\\)"
64                                            (regexp-quote "?=")))
65
66 (defun mime/nth-string (s n)
67   (if (stringp s)
68       (substring s (match-beginning n) (match-end n))
69     (buffer-substring (match-beginning n) (match-end n))))
70
71 (defun mime/encoded-word-charset (str)
72   (mime/nth-string str 1))
73
74 (defun mime/encoded-word-encoding (str)
75   (mime/nth-string str 2))
76
77 (defun mime/encoded-word-encoded-text (str)
78   (mime/nth-string str 3))
79
80 (defun mime/rest-of-string (str)
81   (if (stringp str)
82       (substring str (match-end 0))
83     (buffer-substring (match-end 0)(point-max))
84     ))
85
86
87 ;;; @ variables
88 ;;;
89
90 (defvar mime/no-encoding-header-fields '("X-Nsubject" "Newsgroups"))
91
92 (defvar mime/use-X-Nsubject nil)
93
94
95 ;;; @ Application Interface
96 ;;;
97
98 ;;; @@ MIME header decoders
99 ;;;
100
101 (defun mime/decode-encoded-text (charset encoding str)
102   (let ((dest
103          (cond ((string= "B" encoding)
104                 (base64-decode-string str))
105                ((string= "Q" encoding)
106                 (q-encoding-decode-string str))
107                (t (message "unknown encoding %s" encoding)
108                   nil))))
109     (if dest
110         (mime/convert-string-to-emacs charset dest)
111       )))
112
113 (defun mime/decode-encoded-word (word)
114   (or (if (string-match mime/encoded-word-regexp word)
115           (let ((charset (upcase (mime/encoded-word-charset word)))
116                 (encoding (upcase (mime/encoded-word-encoding word)))
117                 (text (mime/encoded-word-encoded-text word)))
118             (mime/decode-encoded-text charset encoding text)
119             ))
120       word))
121
122 (defun mime/decode-region (beg end)
123   (interactive "*r")
124   (save-excursion
125     (save-restriction
126       (narrow-to-region beg end)
127       (goto-char (point-min))
128       (let (charset encoding text)
129         (while (re-search-forward mime/encoded-word-regexp nil t)
130           (insert (mime/decode-encoded-word 
131                    (prog1
132                        (buffer-substring (match-beginning 0) (match-end 0))
133                      (delete-region (match-beginning 0) (match-end 0))
134                      )
135                   ))
136           ))
137       )))
138
139 (defun mime/decode-message-header ()
140   (interactive "*")
141   (save-excursion
142     (save-restriction
143       (narrow-to-region (goto-char (point-min))
144                         (progn (re-search-forward "^$" nil t) (point)))
145       (mime/prepare-decode-message-header)
146       (mime/decode-region (point-min) (point-max))
147       )))
148
149 (defun mime/decode-string (str)
150   (let ((dest "")(ew nil)
151         beg end)
152     (while (setq beg (string-match mime/encoded-word-regexp str))
153       (if (> beg 0)
154           (if (not (and (eq ew t) (string= (substring str 0 beg) " ")))
155               (setq dest (concat dest (substring str 0 beg)
156                                  ))
157             )
158         )
159       (setq end (match-end 0))
160       (setq dest (concat dest
161                          (mime/decode-encoded-word (substring str beg end))
162                          ))
163       (setq str (substring str end))
164       (setq ew t)
165       )
166     (concat dest str)
167     ))
168
169 ;;; @@ MIME header encoders
170 ;;;
171
172 (defun mime/encode-string (string encoding &optional mode)
173   (cond ((string= encoding "B") (base64-encode-string string))
174         ((string= encoding "Q") (q-encoding-encode-string string mode))
175         ))
176
177 (defun mime/encode-field (str)
178   (setq str (message/unfolding-string str))
179   (let ((ret (message/divide-field str))
180         field-name field-body)
181     (setq field-name (car ret))
182     (setq field-body (nth 1 ret))
183     (concat field-name " "
184             (cond ((string= field-body "") "")
185                   ((or (string-match "^Reply-To:$" field-name)
186                        (string-match "^From:$" field-name)
187                        (string-match "^Sender:$" field-name)
188                        (string-match "^Resent-Reply-To:$" field-name)
189                        (string-match "^Resent-From:$" field-name)
190                        (string-match "^Resent-Sender:$" field-name)
191                        (string-match "^To:$" field-name)
192                        (string-match "^Resent-To:$" field-name)
193                        (string-match "^cc:$" field-name)
194                        (string-match "^Resent-cc:$" field-name)
195                        (string-match "^bcc:$" field-name)
196                        (string-match "^Resent-bcc:$" field-name)
197                        )
198                    (mime/encode-address-list
199                     (+ (length field-name) 1) field-body)
200                    )
201                   (t
202                    (catch 'tag
203                      (let ((r mime/no-encoding-header-fields) fn)
204                        (while r
205                          (setq fn (car r))
206                          (if (string-match (concat "^" fn ":$") field-name)
207                              (throw 'tag field-body)
208                            )
209                          (setq r (cdr r))
210                          ))
211                      (nth 1 (mime/encode-header-string
212                              (+ (length field-name) 1) field-body))
213                      ))
214                   ))
215     ))
216
217 (defun mime/encode-message-header ()
218   (interactive "*")
219   (save-excursion
220     (save-restriction
221       (narrow-to-region (goto-char (point-min))
222                         (progn
223                           (re-search-forward
224                            (concat
225                             "^" (regexp-quote mail-header-separator) "$")
226                            nil t)
227                           (match-beginning 0)
228                           ))
229       (goto-char (point-min))
230       (let (beg end field)
231         (while (re-search-forward "^.+:.*\\(\n\\s +.*\\)*" nil t)
232           (setq beg (match-beginning 0))
233           (setq end  (match-end 0))
234           (setq field (buffer-substring beg end))
235           (insert (mime/encode-field
236                    (prog1
237                        (buffer-substring beg end)
238                      (delete-region beg end)
239                      )))
240           ))
241       (if mime/use-X-Nsubject
242           (progn
243             (goto-char (point-min))
244             (if (re-search-forward "^Subject:.*\\(\n\\s +.*\\)*" nil t)
245                 (let ((str (buffer-substring (match-beginning 0)
246                                              (match-end 0))))
247                   (if (string-match mime/encoded-word-regexp str)
248                       (insert (concat
249                                "\nX-Nsubject: "
250                                (nth 1 (message/divide-field
251                                        (mime/decode-string
252                                         (message/unfolding-string str))
253                                        ))))
254                     ))
255               )))
256       )))
257
258
259 ;;; @ functions for message header encoding
260 ;;;
261
262 (defun mime/encode-and-split-string (n string charset encoding)
263   (let ((i 0) (j 0)
264         (len (length string))
265         (js (mime/convert-string-from-emacs string charset))
266         (cesl (+ (length charset) (length encoding) 6 ))
267         ewl m rest)
268     (setq ewl (mime/encoded-word-length js encoding))
269     (if (null ewl) nil
270       (progn
271         (setq m (+ n ewl cesl))
272         (if (> m 76)
273             (progn
274               (while (and (< i len)
275                           (setq js (mime/convert-string-from-emacs
276                                     (substring string 0 i) charset))
277                           (setq m (+ n
278                                      (mime/encoded-word-length js encoding)
279                                      cesl))
280                           (< m 76))
281                 (setq j i)
282                 (setq i (+ i (char-bytes (elt string i))))
283                 )
284               (setq js (mime/convert-string-from-emacs
285                         (substring string 0 j) charset))
286               (setq m (+ n (mime/encoded-word-length js encoding) cesl))
287               (setq rest (substring string j))
288               )
289           (setq rest nil))
290         (if (string= js "")
291             (list 1 "" string)
292           (list m (concat "=?" charset "?" encoding "?"
293                           (mime/encode-string js encoding)
294                           "?=") rest))
295         ))
296     ))
297
298 (defun mime/encode-header-word (n string charset encoding)
299   (let (dest str ret m)
300     (if (null (setq ret
301                     (mime/encode-and-split-string n string charset encoding)))
302         nil
303       (progn
304         (setq dest (nth 1 ret))
305         (setq m (car ret))
306         (setq str (nth 2 ret))
307         (while (and (stringp str)
308                     (setq ret
309                           (mime/encode-and-split-string
310                            1 str charset encoding))
311                     )
312           (setq dest (concat dest "\n " (nth 1 ret)))
313           (setq m (car ret))
314           (setq str (nth 2 ret))
315           )
316         (list m dest)
317         ))
318     ))
319
320 (defun mime/encode-header-string (n string &optional mode)
321   (if (string= string "")
322       (list n "")
323     (let ((ssl (mime/separate-string-for-encoder string))
324           i len cell et w ew (dest "") b l)
325       (setq len (length ssl))
326       (setq cell (nth 0 ssl))
327       (setq et (car cell))
328       ;; string-width crashes when the argument is nil,
329       ;; so replace the argument
330       ;; (original modification by Kenji Rikitake 9-JAN-1995)
331       (setq w (or (cdr cell) ""))
332       (if (eq et nil)
333           (progn
334             (if (> (+ n (string-width w)) 76)
335                 (progn
336                   (setq dest (concat dest "\n "))
337                   (setq b 1)
338                   )
339               (setq b n))
340             (setq dest (concat dest w))
341             (setq b (+ b (string-width w)))
342             )
343         (progn
344           (setq ew (mime/encode-header-word n (cdr cell) (car et) (cdr et)))
345           (setq dest (nth 1 ew))
346           (setq b (car ew))
347           ))
348       (setq i 1)
349       (while (< i len)
350         (setq cell (nth i ssl))
351         (setq et (car cell))
352         (setq w (cdr cell))
353         (cond ((string-match "^[ \t]*$" w)
354                (setq b (+ b (string-width (cdr cell))))
355                (setq dest (concat dest (cdr cell)))
356                )
357               ((eq et nil)
358                (if (> (+ b (string-width w)) 76)
359                    (progn
360                      (if (eq (elt dest (- (length dest) 1)) 32)
361                          (setq dest (substring dest 0 (- (length dest) 1)))
362                        )
363                      (setq dest (concat dest "\n " w))
364                      (setq b (+ (length w) 1))
365                      )
366                  (setq l (length dest))
367                  (if (and (>= l 2)
368                           (eq (elt dest (- l 2)) ?\?)
369                           (eq (elt dest (- l 1)) ?=)
370                           )
371                      (progn
372                        (setq dest (concat dest " "))
373                        (setq b (+ b 1))
374                        ))
375                  (setq dest (concat dest w))
376                  (setq b (+ b (string-width w)))
377                  ))
378               (t
379                (if (not (eq (elt dest (- (length dest) 1)) 32))
380                    (progn
381                      (setq dest (concat dest " "))
382                      (setq b (+ b 1))
383                      ))
384                (setq ew
385                      (mime/encode-header-word b (cdr cell) (car et) (cdr et)))
386                (setq b (car ew)) 
387                (if (string-match "^\n" (nth 1 ew))
388                    (setq dest (concat (substring dest 0 (- (length dest) 1))
389                                       (nth 1 ew)))
390                  (setq dest (concat dest (nth 1 ew)))
391                  )
392                ))
393         (setq i (+ i 1))
394         )
395       (list b dest)
396       )))
397
398 (defun mime/encode-address-list (n str)
399   (let* ((ret (message/parse-addresses str))
400          (r ret) cell en-ret j cl (dest "") s)
401     (while r
402       (setq cell (car r))
403       (cond ((string= (nth 1 cell) "<")
404              (setq en-ret (mime/encode-header-string n (nth 0 cell) 'phrase))
405              (setq dest (concat dest (nth 1 en-ret)))
406              (setq n (car en-ret))
407              (if (> (length r) 1)
408                  (setq en-ret
409                        (mime/encode-header-string
410                         n (concat (nth 1 cell)(nth 2 cell)(nth 3 cell) ", "))) 
411                (setq en-ret (mime/encode-header-string
412                              n (concat (nth 1 cell)(nth 2 cell)(nth 3 cell))))
413                )
414              (if (and (eq (elt (nth 1 en-ret) 0) ?\n)
415                       (eq (elt dest (- (length dest) 1)) 32))
416                  (setq dest (substring dest 0 (- (length dest) 1)))
417                )
418              (setq dest (concat dest (nth 1 en-ret)))
419              (setq n (car en-ret))
420              )
421             ((= (length cell) 4)
422              (setq en-ret (mime/encode-header-string n (nth 0 cell)))
423              (setq dest (concat dest (nth 1 en-ret)))
424              (setq n (car en-ret))
425              
426              (setq en-ret (mime/encode-header-string (+ n 2) (nth 2 cell)
427                                                      'comment))
428              (if (eq (elt (nth 1 en-ret) 0) ?\n)
429                  (progn
430                    (setq dest (concat dest "\n ("))
431                    (setq en-ret (mime/encode-header-string 2 (nth 2 cell)
432                                                            'comment))
433                    )
434                (progn
435                  (setq dest (concat dest " ("))
436                  ))
437              (setq dest (concat dest (nth 1 en-ret)))
438              (setq n (car en-ret))
439              (if (> (length r) 1)
440                  (setq en-ret
441                        (mime/encode-header-string n (concat (nth 3 cell) ", "))
442                        )
443                (setq en-ret (mime/encode-header-string n (nth 3 cell)))
444                )
445              (setq dest (concat dest (nth 1 en-ret)))
446              (setq n (car en-ret))
447              )
448             (t
449              (if (> (length r) 1)
450                  (setq en-ret
451                        (mime/encode-header-string n (concat (nth 0 cell) ", "))
452                        )
453                (setq en-ret (mime/encode-header-string n (nth 0 cell)))
454                )
455              (setq dest (concat dest (nth 1 en-ret)))
456              (setq n (car en-ret))
457              ))
458       (setq r (cdr r))
459       )
460     dest))
461
462
463 ;;; @ utility for encoder
464 ;;;
465
466 ;;; @@ encoded-word length
467 ;;;
468
469 (defun mime/encoded-word-length (string encoding)
470   (cond ((equal encoding "B") (mime/base64-length string))
471         ((equal encoding "Q") (mime/Quoted-Printable-length string))
472         (t nil)
473         ))
474
475 (defun mime/base64-length (string)
476   (let ((l (length string))
477         )
478     (* (+ (/ l 3)
479           (if (= (mod l 3) 0) 0 1)
480           ) 4)
481     ))
482
483 (defun mime/Quoted-Printable-length (string &optional mode)
484   (let ((l 0)(i 0)(len (length string)) chr)
485     (while (< i len)
486       (setq chr (elt string i))
487       (if (string-match "[A-Za-z0-9!*+/=_---]" (char-to-string chr))
488           (setq l (+ l 1))
489         (setq l (+ l 3))
490         )
491       (setq i (+ i 1)) )
492     l))
493
494 ;;; @@ separate by character set
495 ;;;
496
497 ;; by mol. 1993/11/2
498 (defconst LC-space 2)
499
500 ;; by mol. 1993/10/16
501 (defun mime/char-type (chr)
502   (if (or (= chr 32)(= chr ?\t))
503       LC-space
504     (get-lc chr)
505     ))
506
507 (defun mime/separate-string-by-chartype (string)
508   (let ((len (length string))
509         (dest nil) (ds "") s
510         pcs i j cs chr)
511     (if (= len 0) nil
512       (progn
513         (setq chr (elt string 0))
514         (setq pcs (mime/char-type chr))
515         (setq i (char-bytes chr))
516         (setq ds (substring string 0 i))
517         (while (< i len)
518           (setq chr (elt string i))
519           (setq cs (mime/char-type chr))
520           (setq j (+ i (char-bytes chr)))
521           (setq s (substring string i j))
522           (setq i j)
523           (if (= cs pcs)
524               (setq ds (concat ds s))
525             (progn (setq dest (append dest (list (cons pcs ds))))
526                    (setq pcs cs)
527                    (setq ds s)
528                    ))
529           )
530         (if (not (string= ds ""))
531             (setq dest (append dest (list (cons pcs ds)))))
532         dest)
533       )))
534
535 (defun mime/separate-string-by-charset (str)
536   (let ((rl (mime/separate-string-by-chartype str))
537         (i 1) len (pcell nil) cell ncell dpcell (dest nil) LC)
538     (setq len (length rl))
539     (setq dpcell (list (nth 0 rl)))
540     (setq cell (nth 1 rl))
541     (setq ncell (nth 2 rl))
542     (while (< i len)
543       (setq LC (car (car dpcell)))
544       (cond ((and (not (eq LC lc-ascii))
545                   (eq (car cell) LC-space)
546                   (not (eq (car ncell) lc-ascii)))
547              (setq dpcell (list (cons LC
548                                       (concat (cdr (car dpcell)) (cdr cell))
549                                       )))
550              )
551             ((and (not (eq LC lc-ascii))
552                   (eq LC (car cell)))
553              (setq dpcell (list (cons LC
554                                       (concat (cdr (car dpcell)) (cdr cell))
555                                       )))
556              )
557             ((and (eq LC lc-ascii)
558                   (member (car cell) mime/latin-lc-list))
559              (setq dpcell (list (cons (car cell)
560                                       (concat (cdr (car dpcell)) (cdr cell))
561                                       )))
562              )
563             ((and (member LC mime/latin-lc-list)
564                   (eq (car cell) lc-ascii))
565              (setq dpcell (list (cons LC
566                                       (concat (cdr (car dpcell)) (cdr cell))
567                                       )))
568              )
569             (t
570              (setq dest (append dest dpcell))
571              (setq dpcell (list cell))
572              ))
573       (setq i (+ i 1))
574       (setq cell ncell)
575       (setq ncell (nth (+ i 1) rl))
576       )
577     (setq dest (append dest dpcell))
578     ))
579
580 (defun mime/separate-string-for-encoder (string)
581   (let (lastspace)
582     (if (string-match "[ \t]+$" string)
583         (progn
584           (setq lastspace (substring string
585                                      (match-beginning 0)
586                                      (match-end 0)))
587           (setq string (substring string 0 (match-beginning 0)))
588           ))
589     (let ((rl (mime/separate-string-by-charset string))
590           (i 0) len cell0 cell1 cell2 (dest nil))
591       (setq len (length rl))
592       (setq cell0 (nth 0 rl))
593       (setq cell1 (nth 1 rl))
594       (setq cell2 (nth 2 rl))
595       (while (< i len)
596         (cond ((and (not (eq (car cell0) lc-ascii))
597                     (eq (car cell1) LC-space)
598                     (not (eq (car cell2) lc-ascii))
599                     )
600                (setq dest
601                      (append dest (list
602                                    (cons
603                                     (cdr (assoc (car cell0)
604                                                 mime/lc-charset-and-encoding-alist))
605                                     (concat (cdr cell0) (cdr cell1))
606                                     ))))
607                (setq i (+ i 2))
608                (setq cell0 (nth i rl))
609                (setq cell1 (nth (+ i 1) rl))
610                (setq cell2 (nth (+ i 2) rl))
611                )
612               (t
613                (setq dest
614                      (append dest (list
615                                    (cons
616                                     (cdr (assoc (car cell0)
617                                                 mime/lc-charset-and-encoding-alist))
618                                     (cdr cell0)))))
619                (setq i (+ i 1))
620                (setq cell0 cell1)
621                (setq cell1 cell2)
622                (setq cell2 (nth (+ i 2) rl))
623                ))
624         )
625       (append dest
626               (if lastspace
627                   (list (cons nil lastspace))))
628       )))
629               
630               
631
632 ;;;
633 ;;; basic functions for MIME header decoder
634 ;;;
635
636 ;;; @ utility for decoder
637 ;;;
638
639 (defun mime/unfolding ()
640   (goto-char (point-min))
641   (let (field beg end)
642     (while (re-search-forward message/field-name-regexp nil t)
643       (setq beg (match-beginning 0))
644       (setq end (progn
645                   (if (re-search-forward "\n[!-9;-~]+:" nil t)
646                       (goto-char (match-beginning 0))
647                     (if (re-search-forward "^$" nil t)
648                         (goto-char (1- (match-beginning 0)))
649                       (end-of-line)
650                       ))
651                   (point)
652                   ))
653       (setq field (buffer-substring beg end))
654       (if (string-match mime/encoded-word-regexp field)
655           (save-restriction
656             (narrow-to-region (goto-char beg) end)
657             (while (re-search-forward "\n[ \t]+" nil t)
658               (replace-match " ")
659               )
660             (goto-char (point-max))
661             ))
662       )))
663
664 (defun mime/prepare-decode-message-header ()
665   (mime/unfolding)
666   (goto-char (point-min))
667   (while (re-search-forward
668           (concat (regexp-quote "?=")
669                   "\\s +"
670                   (regexp-quote "=?"))
671           nil t)
672     (replace-match "?==?")
673     )
674   )
675
676 (run-hooks 'mime/tiny-mime-load-hook)
677
678 (provide 'tiny-mime)
679
680 ;;; @
681 ;;; Local Variables:
682 ;;; mode: emacs-lisp
683 ;;; mode: outline-minor
684 ;;; outline-regexp: ";;; @+\\|(......"
685 ;;; End: