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