Merge semi21-D20010129.
[elisp/lemi.git] / mime / eword-encode.el
1 ;;; eword-encode.el --- RFC 2047 based encoded-word encoder for GNU Emacs
2
3 ;; Copyright (C) 1995,1996,1997,1998,1999,2000 Free Software Foundation, Inc.
4
5 ;; Author: MORIOKA Tomohiko <tomo@m17n.org>
6 ;; Keywords: encoded-word, MIME, multilingual, header, mail, news
7
8 ;; This file is part of FLIM (Faithful Library about Internet Message).
9
10 ;; This program is free software; you can redistribute it and/or
11 ;; modify it under the terms of the GNU General Public License as
12 ;; published by the Free Software Foundation; either version 2, or (at
13 ;; your option) any later version.
14
15 ;; This program is distributed in the hope that it will be useful, but
16 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
17 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18 ;; General Public License for more details.
19
20 ;; You should have received a copy of the GNU General Public License
21 ;; along with GNU Emacs; see the file COPYING.  If not, write to the
22 ;; Free Software Foundation, Inc., 59 Temple Place - Suite 330,
23 ;; Boston, MA 02111-1307, USA.
24
25 ;;; Code:
26
27 (require 'mime-def)
28 (require 'mel)
29 (require 'std11)
30 (require 'eword-decode)
31
32
33 ;;; @ variables
34 ;;;
35
36 ;; User options are defined in mime-def.el.
37
38 (defvar mime-header-charset-encoding-alist
39   '((us-ascii           . nil)
40     (iso-8859-1         . "Q")
41     (iso-8859-2         . "Q")
42     (iso-8859-3         . "Q")
43     (iso-8859-4         . "Q")
44     (iso-8859-5         . "Q")
45     (koi8-r             . "Q")
46     (iso-8859-7         . "Q")
47     (iso-8859-8         . "Q")
48     (iso-8859-9         . "Q")
49     (iso-2022-jp        . "B")
50     (iso-2022-jp-3      . "B")
51     (iso-2022-kr        . "B")
52     (gb2312             . "B")
53     (cn-gb              . "B")
54     (cn-gb-2312         . "B")
55     (euc-kr             . "B")
56     (tis-620            . "B")
57     (iso-2022-jp-2      . "B")
58     (iso-2022-int-1     . "B")
59     (utf-8              . "B")
60     ))
61
62 (defvar mime-header-default-charset-encoding "Q")
63
64
65 ;;; @ encoded-text encoder
66 ;;;
67
68 (defun eword-encode-text (charset encoding string &optional mode)
69   "Encode STRING as an encoded-word, and return the result.
70 CHARSET is a symbol to indicate MIME charset of the encoded-word.
71 ENCODING allows \"B\" or \"Q\".
72 MODE is allows `text', `comment', `phrase' or nil.  Default value is
73 `phrase'."
74   (let ((text (encoded-text-encode-string string encoding mode)))
75     (if text
76         (concat "=?" (upcase (symbol-name charset)) "?"
77                 encoding "?" text "?=")
78       )))
79
80
81 ;;; @ charset word
82 ;;;
83
84 (defsubst eword-encode-char-type (character)
85   (if (memq character '(?  ?\t ?\n))
86       nil
87     (char-charset character)
88     ))
89
90 (defun eword-encode-divide-into-charset-words (string)
91   (let ((len (length string))
92         dest)
93     (while (> len 0)
94       (let* ((chr (aref string 0))
95              ;; (chr (sref string 0))
96              (charset (eword-encode-char-type chr))
97              (i 1)
98              ;; (i (char-length chr))
99              )
100         (while (and (< i len)
101                     (setq chr (aref string i))
102                     ;; (setq chr (sref string i))
103                     (eq charset (eword-encode-char-type chr)))
104           (setq i (1+ i))
105           ;; (setq i (char-next-index chr i))
106           )
107         (setq dest (cons (cons charset (substring string 0 i)) dest)
108               string (substring string i)
109               len (- len i))))
110     (nreverse dest)))
111
112
113 ;;; @ word
114 ;;;
115
116 (defun eword-encode-charset-words-to-words (charset-words)
117   (let (dest)
118     (while charset-words
119       (let* ((charset-word (car charset-words))
120              (charset (car charset-word))
121              )
122         (if charset
123             (let ((charsets (list charset))
124                   (str (cdr charset-word))
125                   )
126               (catch 'tag
127                 (while (setq charset-words (cdr charset-words))
128                   (setq charset-word (car charset-words)
129                         charset (car charset-word))
130                   (if (null charset)
131                       (throw 'tag nil)
132                     )
133                   (or (memq charset charsets)
134                       (setq charsets (cons charset charsets))
135                       )
136                   (setq str (concat str (cdr charset-word)))
137                   ))
138               (setq dest (cons (cons charsets str) dest))
139               )
140           (setq dest (cons charset-word dest)
141                 charset-words (cdr charset-words)
142                 ))))
143     (nreverse dest)
144     ))
145
146
147 ;;; @ rule
148 ;;;
149
150 (defmacro make-ew-rword (text charset encoding type)
151   (` (list (, text)(, charset)(, encoding)(, type))))
152 (defmacro ew-rword-text (rword)
153   (` (car (, rword))))
154 (defmacro ew-rword-charset (rword)
155   (` (car (cdr (, rword)))))
156 (defmacro ew-rword-encoding (rword)
157   (` (car (cdr (cdr (, rword))))))
158 (defmacro ew-rword-type (rword)
159   (` (car (cdr (cdr (cdr (, rword)))))))
160
161 (defun ew-find-charset-rule (charsets)
162   (if charsets
163       (let* ((charset (find-mime-charset-by-charsets charsets))
164              (encoding
165               (cdr (or (assq charset mime-header-charset-encoding-alist)
166                        (cons charset mime-header-default-charset-encoding)))))
167         (list charset encoding))))
168
169 (defun tm-eword::words-to-ruled-words (wl &optional mode)
170   (mapcar (function
171            (lambda (word)
172              (let ((ret (ew-find-charset-rule (car word))))
173                (make-ew-rword (cdr word) (car ret)(nth 1 ret) mode)
174                )))
175           wl))
176
177 (defun ew-space-process (seq)
178   (let (prev a ac b c cc)
179     (while seq
180       (setq b (car seq))
181       (setq seq (cdr seq))
182       (setq c (car seq))
183       (setq cc (ew-rword-charset c))
184       (if (and (null (ew-rword-charset b))
185                (not (eq (ew-rword-type b) 'special)))
186           (progn
187             (setq a (car prev))
188             (setq ac (ew-rword-charset a))
189             (if (and (ew-rword-encoding a)
190                      (ew-rword-encoding c))
191                 (cond ((eq ac cc)
192                        (setq prev (cons
193                                    (cons (concat (car a)(car b)(car c))
194                                          (cdr a))
195                                    (cdr prev)
196                                    ))
197                        (setq seq (cdr seq))
198                        )
199                       (t
200                        (setq prev (cons
201                                    (cons (concat (car a)(car b))
202                                          (cdr a))
203                                    (cdr prev)
204                                    ))
205                        ))
206               (setq prev (cons b prev))
207               ))
208         (setq prev (cons b prev))
209         ))
210     (reverse prev)
211     ))
212
213 (defun eword-encode-split-string (str &optional mode)
214   (ew-space-process
215    (tm-eword::words-to-ruled-words
216     (eword-encode-charset-words-to-words
217      (eword-encode-divide-into-charset-words str))
218     mode)))
219
220
221 ;;; @ length
222 ;;;
223
224 (defun tm-eword::encoded-word-length (rword)
225   (let ((string   (ew-rword-text     rword))
226         (charset  (ew-rword-charset  rword))
227         (encoding (ew-rword-encoding rword))
228         ret)
229     (setq ret
230           (cond ((string-equal encoding "B")
231                  (setq string (encode-mime-charset-string string charset))
232                  (base64-encoded-length string)
233                  )
234                 ((string-equal encoding "Q")
235                  (setq string (encode-mime-charset-string string charset))
236                  (Q-encoded-text-length string (ew-rword-type rword))
237                  )))
238     (if ret
239         (cons (+ 7 (length (symbol-name charset)) ret) string)
240       )))
241
242
243 ;;; @ encode-string
244 ;;;
245
246 (defun ew-encode-rword-1 (column rwl &optional must-output)
247   (catch 'can-not-output
248     (let* ((rword (car rwl))
249            (ret (tm-eword::encoded-word-length rword))
250            string len)
251       (if (null ret)
252           (cond ((and (setq string (car rword))
253                       (or (<= (setq len (+ (length string) column)) 76)
254                           (<= column 1))
255                       )
256                  (setq rwl (cdr rwl))
257                  )
258                 ((memq (aref string 0) '(?  ?\t))
259                  (setq string (concat "\n" string)
260                        len (length string)
261                        rwl (cdr rwl))
262                  )
263                 (must-output
264                  (setq string "\n "
265                        len 1)
266                  )
267                 (t
268                  (throw 'can-not-output nil)
269                  ))
270         (cond ((and (setq len (car ret))
271                     (<= (+ column len) 76)
272                     )
273                (setq string
274                      (eword-encode-text
275                       (ew-rword-charset rword)
276                       (ew-rword-encoding rword)
277                       (cdr ret)
278                       (ew-rword-type rword)
279                       ))
280                (setq len (+ (length string) column))
281                (setq rwl (cdr rwl))
282                )
283               (t
284                (setq string (car rword))
285                (let* ((p 0) np
286                       (str "") nstr)
287                  (while (and (< p len)
288                              (progn
289                                (setq np (1+ p))
290                                ;;(setq np (char-next-index (sref string p) p))
291                                (setq nstr (substring string 0 np))
292                                (setq ret (tm-eword::encoded-word-length
293                                           (cons nstr (cdr rword))
294                                           ))
295                                (setq nstr (cdr ret))
296                                (setq len (+ (car ret) column))
297                                (<= len 76)
298                                ))
299                    (setq str nstr
300                          p np))
301                  (if (string-equal str "")
302                      (if must-output
303                          (setq string "\n "
304                                len 1)
305                        (throw 'can-not-output nil))
306                    (setq rwl (cons (cons (substring string p) (cdr rword))
307                                    (cdr rwl)))
308                    (setq string
309                          (eword-encode-text
310                           (ew-rword-charset rword)
311                           (ew-rword-encoding rword)
312                           str
313                           (ew-rword-type rword)))
314                    (setq len (+ (length string) column))
315                    )
316                  )))
317         )
318       (list string len rwl)
319       )))
320
321 (defun eword-encode-rword-list (column rwl)
322   (let (ret dest str ew-f pew-f folded-points)
323     (while rwl
324       (setq ew-f (nth 2 (car rwl)))
325       (if (and pew-f ew-f)
326           (setq rwl (cons '(" ") rwl)
327                 pew-f nil)
328         (setq pew-f ew-f)
329         )
330       (if (null (setq ret (ew-encode-rword-1 column rwl)))
331           (let ((i (1- (length dest)))
332                 c s r-dest r-column)
333             (catch 'success
334               (while (catch 'found
335                        (while (>= i 0)
336                          (cond ((memq (setq c (aref dest i)) '(?  ?\t))
337                                 (if (memq i folded-points)
338                                     (throw 'found nil)
339                                   (setq folded-points (cons i folded-points))
340                                   (throw 'found i))
341                                 )
342                                ((eq c ?\n)
343                                 (throw 'found nil)
344                                 ))
345                          (setq i (1- i))))
346                 (setq s (substring dest i)
347                       r-column (length s)
348                       r-dest (concat (substring dest 0 i) "\n" s))
349                 (when (setq ret (ew-encode-rword-1 r-column rwl))
350                   (setq dest r-dest
351                         column r-column)
352                   (throw 'success t)
353                   ))
354               (setq ret (ew-encode-rword-1 column rwl 'must-output))
355               )))
356       (setq str (car ret))
357       (setq dest (concat dest str))
358       (setq column (nth 1 ret)
359             rwl (nth 2 ret))
360       )
361     (list dest column)
362     ))
363
364
365 ;;; @ converter
366 ;;;
367
368 (defun eword-encode-phrase-to-rword-list (phrase)
369   (let (token type dest str)
370     (while phrase
371       (setq token (car phrase))
372       (setq type (car token))
373       (cond ((eq type 'quoted-string)
374              (setq str (concat "\"" (cdr token) "\""))
375              (setq dest
376                    (append dest
377                            (list
378                             (let ((ret (ew-find-charset-rule
379                                         (find-charset-string str))))
380                               (make-ew-rword
381                                str (car ret)(nth 1 ret) 'phrase)
382                               )
383                             )))
384              )
385             ((eq type 'comment)
386              (setq dest
387                    (append dest
388                            '(("(" nil nil special))
389                            (tm-eword::words-to-ruled-words
390                             (eword-encode-charset-words-to-words
391                              (eword-encode-divide-into-charset-words
392                               (cdr token)))
393                             'comment)
394                            '((")" nil nil special))
395                            ))
396              )
397             (t
398              (setq dest
399                    (append dest
400                            (tm-eword::words-to-ruled-words
401                             (eword-encode-charset-words-to-words
402                              (eword-encode-divide-into-charset-words
403                               (cdr token))
404                              ) 'phrase)))
405              ))
406       (setq phrase (cdr phrase))
407       )
408     (ew-space-process dest)
409     ))
410
411 (defun eword-encode-addr-seq-to-rword-list (seq)
412   (let (dest pname)
413     (while seq
414       (let* ((token (car seq))
415              (name (car token))
416              )
417         (cond ((eq name 'spaces)
418                (setq dest (nconc dest (list (list (cdr token) nil nil))))
419                )
420               ((eq name 'comment)
421                (setq dest
422                      (nconc
423                       dest
424                       (list (list "(" nil nil))
425                       (eword-encode-split-string (cdr token) 'comment)
426                       (list (list ")" nil nil))
427                       ))
428                )
429               ((eq name 'quoted-string)
430                (setq dest
431                      (nconc
432                       dest
433                       (list
434                        (list (concat "\"" (cdr token) "\"") nil nil)
435                        )))
436                )
437               (t
438                (setq dest
439                      (if (or (eq pname 'spaces)
440                              (eq pname 'comment))
441                          (nconc dest (list (list (cdr token) nil nil)))
442                        (nconc (nreverse (cdr (reverse dest)))
443                               ;; (butlast dest)
444                               (list
445                                (list (concat (car (car (last dest)))
446                                              (cdr token))
447                                      nil nil)))))
448                ))
449         (setq seq (cdr seq)
450               pname name))
451       )
452     dest))
453
454 (defun eword-encode-phrase-route-addr-to-rword-list (phrase-route-addr)
455   (if (eq (car phrase-route-addr) 'phrase-route-addr)
456       (let ((phrase (nth 1 phrase-route-addr))
457             (route (nth 2 phrase-route-addr))
458             dest)
459         ;; (if (eq (car (car phrase)) 'spaces)
460         ;;     (setq phrase (cdr phrase))
461         ;;   )
462         (setq dest (eword-encode-phrase-to-rword-list phrase))
463         (if dest
464             (setq dest (append dest '((" " nil nil))))
465           )
466         (append
467          dest
468          (eword-encode-addr-seq-to-rword-list
469           (append '((specials . "<"))
470                   route
471                   '((specials . ">"))))
472          ))))
473
474 (defun eword-encode-addr-spec-to-rword-list (addr-spec)
475   (if (eq (car addr-spec) 'addr-spec)
476       (eword-encode-addr-seq-to-rword-list (cdr addr-spec))
477     ))
478
479 (defun eword-encode-mailbox-to-rword-list (mbox)
480   (let ((addr (nth 1 mbox))
481         (comment (nth 2 mbox))
482         dest)
483     (setq dest (or (eword-encode-phrase-route-addr-to-rword-list addr)
484                    (eword-encode-addr-spec-to-rword-list addr)
485                    ))
486     (if comment
487         (setq dest
488               (append dest
489                       '((" " nil nil)
490                         ("(" nil nil))
491                       (eword-encode-split-string comment 'comment)
492                       (list '(")" nil nil))
493                       )))
494     dest))
495
496 (defsubst eword-encode-mailboxes-to-rword-list (mboxes)
497   (let ((dest (eword-encode-mailbox-to-rword-list (car mboxes))))
498     (if dest
499         (while (setq mboxes (cdr mboxes))
500           (setq dest
501                 (nconc dest
502                        (list '("," nil nil))
503                        (eword-encode-mailbox-to-rword-list
504                         (car mboxes))))))
505     dest))
506
507 (defsubst eword-encode-address-to-rword-list (address)
508   (cond
509    ((eq (car address) 'mailbox)
510     (eword-encode-mailbox-to-rword-list address))
511    ((eq (car address) 'group)
512     (nconc
513      (eword-encode-phrase-to-rword-list (nth 1 address))
514      (list (list ":" nil nil))
515      (eword-encode-mailboxes-to-rword-list (nth 2 address))
516      (list (list ";" nil nil))))))
517
518 (defsubst eword-encode-addresses-to-rword-list (addresses)
519   (let ((dest (eword-encode-address-to-rword-list (car addresses))))
520     (if dest
521         (while (setq addresses (cdr addresses))
522           (setq dest
523                 (nconc dest
524                        (list '("," nil nil))
525                        ;; (list '(" " nil nil))
526                        (eword-encode-address-to-rword-list (car addresses))))))
527     dest))
528
529 (defsubst eword-encode-msg-id-to-rword-list (msg-id)
530   (list
531    (list
532     (concat "<"
533             (caar (eword-encode-addr-seq-to-rword-list (cdr msg-id)))
534             ">")
535     nil nil)))
536
537 (defsubst eword-encode-in-reply-to-to-rword-list (in-reply-to)
538   (let (dest)
539     (while in-reply-to
540       (setq dest
541             (append dest
542                     (let ((elt (car in-reply-to)))
543                       (if (eq (car elt) 'phrase)
544                           (eword-encode-phrase-to-rword-list (cdr elt))
545                         (eword-encode-msg-id-to-rword-list elt)
546                         ))))
547       (setq in-reply-to (cdr in-reply-to)))
548     dest))
549
550
551 ;;; @ application interfaces
552 ;;;
553
554 (defvar eword-encode-default-start-column 10
555   "Default start column if it is omitted.")
556
557 (defun eword-encode-string (string &optional column mode)
558   "Encode STRING as encoded-words, and return the result.
559 Optional argument COLUMN is start-position of the field.
560 Optional argument MODE allows `text', `comment', `phrase' or nil.
561 Default value is `phrase'."
562   (car (eword-encode-rword-list
563         (or column eword-encode-default-start-column)
564         (eword-encode-split-string string mode))))
565
566 (defun eword-encode-address-list (string &optional column)
567   "Encode header field STRING as list of address, and return the result.
568 Optional argument COLUMN is start-position of the field."
569   (car (eword-encode-rword-list
570         (or column eword-encode-default-start-column)
571         (eword-encode-addresses-to-rword-list
572          (std11-parse-addresses-string string))
573         )))
574
575 (defun eword-encode-in-reply-to (string &optional column)
576   "Encode header field STRING as In-Reply-To field, and return the result.
577 Optional argument COLUMN is start-position of the field."
578   (car (eword-encode-rword-list
579         (or column 13)
580         (eword-encode-in-reply-to-to-rword-list
581          (std11-parse-msg-ids-string string)))))
582
583 (defun eword-encode-structured-field-body (string &optional column)
584   "Encode header field STRING as structured field, and return the result.
585 Optional argument COLUMN is start-position of the field."
586   (car (eword-encode-rword-list
587         (or column eword-encode-default-start-column)
588         (eword-encode-addr-seq-to-rword-list (std11-lexical-analyze string))
589         )))
590
591 (defun eword-encode-unstructured-field-body (string &optional column)
592   "Encode header field STRING as unstructured field, and return the result.
593 Optional argument COLUMN is start-position of the field."
594   (car (eword-encode-rword-list
595         (or column eword-encode-default-start-column)
596         (eword-encode-split-string string 'text))))
597
598 ;;;###autoload
599 (defun mime-encode-field-body (field-body field-name)
600   "Encode FIELD-BODY as FIELD-NAME, and return the result.
601 A lexical token includes non-ASCII character is encoded as MIME
602 encoded-word.  ASCII token is not encoded."
603   (setq field-body (std11-unfold-string field-body))
604   (if (string= field-body "")
605       ""
606     (let (start)
607       (if (symbolp field-name)
608           (setq start (1+ (length (symbol-name field-name))))
609         (setq start (1+ (length field-name))
610               field-name (intern (capitalize field-name))))
611       (cond ((memq field-name
612                    '(Reply-To
613                      From Sender
614                      Resent-Reply-To Resent-From
615                      Resent-Sender To Resent-To
616                      Cc Resent-Cc Bcc Resent-Bcc
617                      Dcc))
618              (eword-encode-address-list field-body start))
619             ((eq field-name 'In-Reply-To)
620              (eword-encode-in-reply-to field-body start))
621             ((memq field-name '(Mime-Version User-Agent))
622              (eword-encode-structured-field-body field-body start))
623             (t
624              (eword-encode-unstructured-field-body field-body start))))))
625 (defalias 'eword-encode-field-body 'mime-encode-field-body)
626 (make-obsolete 'eword-encode-field-body 'mime-encode-field-body)
627
628 (defun eword-in-subject-p ()
629   (let ((str (std11-field-body "Subject")))
630     (if (and str (string-match eword-encoded-word-regexp str))
631         str)))
632 (make-obsolete 'eword-in-subject-p "Don't use it.")
633
634 (defsubst eword-find-field-encoding-method (field-name)
635   (setq field-name (downcase field-name))
636   (let ((alist mime-field-encoding-method-alist))
637     (catch 'found
638       (while alist
639         (let* ((pair (car alist))
640                (str (car pair)))
641           (if (and (stringp str)
642                    (string= field-name (downcase str)))
643               (throw 'found (cdr pair))
644             ))
645         (setq alist (cdr alist)))
646       (cdr (assq t mime-field-encoding-method-alist))
647       )))
648
649 ;;;###autoload
650 (defun mime-encode-header-in-buffer (&optional code-conversion)
651   "Encode header fields to network representation, such as MIME encoded-word.
652
653 It refer variable `mime-field-encoding-method-alist'."
654   (interactive "*")
655   (save-excursion
656     (save-restriction
657       (std11-narrow-to-header mail-header-separator)
658       (goto-char (point-min))
659       (let ((default-cs (mime-charset-to-coding-system default-mime-charset))
660             bbeg end field-name)
661         (while (re-search-forward std11-field-head-regexp nil t)
662           (setq bbeg (match-end 0)
663                 field-name (buffer-substring (match-beginning 0) (1- bbeg))
664                 end (std11-field-end))
665           (and (delq 'ascii (find-charset-region bbeg end))
666                (let ((method (eword-find-field-encoding-method
667                               (downcase field-name))))
668                  (cond ((eq method 'mime)
669                         (let ((field-body
670                                (buffer-substring-no-properties bbeg end)
671                                ))
672                           (delete-region bbeg end)
673                           (insert (mime-encode-field-body field-body
674                                                           field-name))))
675                        (code-conversion
676                         (let ((cs
677                                (or (mime-charset-to-coding-system
678                                     method)
679                                    default-cs)))
680                           (encode-coding-region bbeg end cs)
681                           )))
682                  ))
683           ))
684       )))
685 (defalias 'eword-encode-header 'mime-encode-header-in-buffer)
686 (make-obsolete 'eword-encode-header 'mime-encode-header-in-buffer)
687
688
689 ;;; @ end
690 ;;;
691
692 (provide 'eword-encode)
693
694 ;;; eword-encode.el ends here