1461b6531f40b478fcd3fae6319f1b4c1c279256
[elisp/tm.git] / tm-ew-e.el
1 ;;;
2 ;;; tm-ew-e.el --- RFC 1522 based multilingual MIME message header
3 ;;;                encoder for GNU Emacs
4 ;;;
5 ;;; Copyright (C) 1995 Free Software Foundation, Inc.
6 ;;; Copyright (C) 1993 .. 1996 MORIOKA Tomohiko
7 ;;;
8 ;;; Author: MORIOKA Tomohiko <morioka@jaist.ac.jp>
9 ;;; Version: $Revision: 7.37 $
10 ;;; Keywords: mail, news, MIME, RFC 1522, multilingual, encoded-word
11 ;;;
12 ;;; This file is part of tm (Tools for MIME).
13 ;;;
14 ;;; This program is free software; you can redistribute it and/or
15 ;;; modify it under the terms of the GNU General Public License as
16 ;;; published by the Free Software Foundation; either version 2, or
17 ;;; (at your option) any later version.
18 ;;;
19 ;;; This program is distributed in the hope that it will be useful,
20 ;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
21 ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
22 ;;; General Public License for more details.
23 ;;;
24 ;;; You should have received a copy of the GNU General Public License
25 ;;; along with This program.  If not, write to the Free Software
26 ;;; Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
27 ;;;
28 ;;; Code:
29
30 (require 'mel)
31 (require 'tl-822)
32 (require 'tm-def)
33
34
35 ;;; @ version
36 ;;;
37
38 (defconst tm-ew-e/RCS-ID
39   "$Id: tm-ew-e.el,v 7.37 1996/07/10 12:52:46 morioka Exp $")
40 (defconst mime-eword/encoder-version (get-version-string tm-ew-e/RCS-ID))
41
42
43 ;;; @ variables
44 ;;;
45
46 (defvar mime/no-encoding-header-fields '("X-Nsubject" "Newsgroups"))
47
48 (defvar mime/use-X-Nsubject nil)
49
50 (defvar mime-eword/charset-encoding-alist
51   '((us-ascii           . nil)
52     (iso-8859-1         . "Q")
53     (iso-8859-2         . "Q")
54     (iso-8859-3         . "Q")
55     (iso-8859-4         . "Q")
56     (iso-8859-5         . "Q")
57     (koi8-r             . "Q")
58     (iso-8859-7         . "Q")
59     (iso-8859-8         . "Q")
60     (iso-8859-9         . "Q")
61     (iso-2022-jp        . "B")
62     (iso-2022-kr        . "B")
63     (euc-kr             . "B")
64     (iso-2022-jp-2      . "B")
65     (iso-2022-int-1     . "B")
66     ))
67
68 ;;; @ encoded-text encoder
69 ;;;
70
71 (defun tm-eword::encode-encoded-text (charset encoding string &optional mode)
72   (let ((text
73          (cond ((string= encoding "B")
74                 (base64-encode-string string))
75                ((string= encoding "Q")
76                 (q-encoding-encode-string string mode))
77                )
78          ))
79     (if text
80         (concat "=?" (upcase (symbol-name charset)) "?"
81                 encoding "?" text "?=")
82       )))
83
84
85 ;;; @ leading char
86 ;;;
87
88 (defun tm-eword::char-type (chr)
89   (if (or (= chr 32)(= chr ?\t))
90       nil
91     (char-leading-char chr)
92     ))
93
94 (defun tm-eword::parse-lc-word (str)
95   (let* ((chr (sref str 0))
96          (lc (tm-eword::char-type chr))
97          (i (char-length chr))
98          (len (length str))
99          )
100     (while (and (< i len)
101                 (setq chr (sref str i))
102                 (eq lc (tm-eword::char-type chr))
103                 )
104       (setq i (+ i (char-length chr)))
105       )
106     (cons (cons lc (substring str 0 i)) (substring str i))
107     ))
108
109 (defun tm-eword::split-to-lc-words (str)
110   (let (ret dest)
111     (while (and (not (string= str ""))
112                 (setq ret (tm-eword::parse-lc-word str))
113                 )
114       (setq dest (cons (car ret) dest))
115       (setq str (cdr ret))
116       )
117     (reverse dest)
118     ))
119
120
121 ;;; @ word
122 ;;;
123
124 (defun tm-eword::parse-word (lcwl)
125   (let* ((lcw (car lcwl))
126          (lc (car lcw))
127          )
128     (if (null lc)
129         lcwl
130       (let ((lcl (list lc))
131             (str (cdr lcw))
132             )
133         (catch 'tag
134           (while (setq lcwl (cdr lcwl))
135             (setq lcw (car lcwl))
136             (setq lc (car lcw))
137             (if (null lc)
138                 (throw 'tag nil)
139               )
140             (if (not (memq lc lcl))
141                 (setq lcl (cons lc lcl))
142               )
143             (setq str (concat str (cdr lcw)))
144             ))
145         (cons (cons lcl str) lcwl)
146         ))))
147
148 (defun tm-eword::lc-words-to-words (lcwl)
149   (let (ret dest)
150     (while (setq ret (tm-eword::parse-word lcwl))
151       (setq dest (cons (car ret) dest))
152       (setq lcwl (cdr ret))
153       )
154     (reverse dest)
155     ))
156
157
158 ;;; @ rule
159 ;;;
160
161 (defmacro tm-eword::make-rword (text charset encoding type)
162   (` (list (, text)(, charset)(, encoding)(, type))))
163 (defmacro tm-eword::rword-text (rword)
164   (` (car (, rword))))
165 (defmacro tm-eword::rword-charset (rword)
166   (` (car (cdr (, rword)))))
167 (defmacro tm-eword::rword-encoding (rword)
168   (` (car (cdr (cdr (, rword))))))
169 (defmacro tm-eword::rword-type (rword)
170   (` (car (cdr (cdr (cdr (, rword)))))))
171
172 (defun tm-eword::find-charset-rule (charsets)
173   (if charsets
174       (let* ((charset (charsets-to-mime-charset charsets))
175              (encoding (cdr (assq charset mime-eword/charset-encoding-alist)))
176              )
177         (list charset encoding)
178         )))
179
180 (defun tm-eword::words-to-ruled-words (wl &optional mode)
181   (mapcar (function
182            (lambda (word)
183              (let ((ret (tm-eword::find-charset-rule (car word))))
184                (tm-eword::make-rword (cdr word) (car ret)(nth 1 ret) mode)
185                )))
186           wl))
187
188 (defun tm-eword::space-process (seq)
189   (let (prev a ac b c cc)
190     (while seq
191       (setq b (car seq))
192       (setq seq (cdr seq))
193       (setq c (car seq))
194       (setq cc (tm-eword::rword-charset c))
195       (if (null (tm-eword::rword-charset b))
196           (progn
197             (setq a (car prev))
198             (setq ac (tm-eword::rword-charset a))
199             (if (and (tm-eword::rword-encoding a)
200                      (tm-eword::rword-encoding c))
201                 (cond ((eq ac cc)
202                        (setq prev (cons
203                                    (cons (concat (car a)(car b)(car c))
204                                          (cdr a))
205                                    (cdr prev)
206                                    ))
207                        (setq seq (cdr seq))
208                        )
209                       (t
210                        (setq prev (cons
211                                    (cons (concat (car a)(car b))
212                                          (cdr a))
213                                    (cdr prev)
214                                    ))
215                        ))
216               (setq prev (cons b prev))
217               ))
218         (setq prev (cons b prev))
219         ))
220     (reverse prev)
221     ))
222
223 (defun tm-eword::split-string (str &optional mode)
224   (tm-eword::space-process
225    (tm-eword::words-to-ruled-words (tm-eword::lc-words-to-words
226                                     (tm-eword::split-to-lc-words str))
227                                    mode)))
228
229
230 ;;; @ length
231 ;;;
232
233 (defun tm-eword::encoded-word-length (rword)
234   (let ((string   (tm-eword::rword-text     rword))
235         (charset  (tm-eword::rword-charset  rword))
236         (encoding (tm-eword::rword-encoding rword))
237         ret)
238     (setq ret
239           (cond ((string-equal encoding "B")
240                  (setq string (encode-mime-charset-string string charset))
241                  (base64-encoded-length string)
242                  )
243                 ((string-equal encoding "Q")
244                  (setq string (encode-mime-charset-string string charset))
245                  (q-encoding-encoded-length string
246                                             (tm-eword::rword-type rword))
247                  )))
248     (if ret
249         (cons (+ 7 (length (symbol-name charset)) ret) string)
250       )))
251
252
253 ;;; @ encode-string
254 ;;;
255
256 (defun tm-eword::encode-string-1 (column rwl)
257   (let* ((rword (car rwl))
258          (ret (tm-eword::encoded-word-length rword))
259          string len)
260     (if (null ret)
261         (cond ((and (setq string (car rword))
262                     (<= (setq len (+ (length string) column)) 76)
263                     )
264                (setq rwl (cdr rwl))
265                )
266               (t
267                (setq string "\n ")
268                (setq len 1)
269                ))
270       (cond ((and (setq len (car ret))
271                   (<= (+ column len) 76)
272                   )
273              (setq string
274                    (tm-eword::encode-encoded-text
275                     (tm-eword::rword-charset rword)
276                     (tm-eword::rword-encoding rword)
277                     (cdr ret)
278                     (tm-eword::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* ((sl (length string))
286                     (p 0) np
287                     (str "") nstr)
288                (while (and (< p len)
289                            (progn
290                              (setq np (+ p (char-length (sref string 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                    (setq string "\n "
303                          len 1)
304                  (setq rwl (cons (cons (substring string p) (cdr rword))
305                                  (cdr rwl)))
306                  (setq string
307                        (tm-eword::encode-encoded-text
308                         (tm-eword::rword-charset rword)
309                         (tm-eword::rword-encoding rword)
310                         str
311                         (tm-eword::rword-type rword)))
312                  (setq len (+ (length string) column))
313                  )
314                )))
315       )
316     (list string len rwl)
317     ))
318
319 (defun tm-eword::encode-rwl (column rwl)
320   (let (ret dest ps special str ew-f pew-f)
321     (while rwl
322       (setq ew-f (nth 2 (car rwl)))
323       (if (and pew-f ew-f)
324           (setq rwl (cons '(" ") rwl)
325                 pew-f nil)
326         (setq pew-f ew-f)
327         )
328       (setq ret (tm-eword::encode-string-1 column rwl))
329       (setq str (car ret))
330       (if (eq (elt str 0) ?\n)
331           (if (eq special ?\()
332               (progn
333                 (setq dest (concat dest "\n ("))
334                 (setq ret (tm-eword::encode-string-1 2 rwl))
335                 (setq str (car ret))
336                 ))
337         (cond ((eq special 32)
338                (if (string= str "(")
339                    (setq ps t)
340                  (setq dest (concat dest " "))
341                  (setq ps nil)
342                  ))
343               ((eq special ?\()
344                (if ps
345                    (progn
346                      (setq dest (concat dest " ("))
347                      (setq ps nil)
348                      )
349                  (setq dest (concat dest "("))
350                  )
351                )))
352       (cond ((string= str " ")
353              (setq special 32)
354              )
355             ((string= str "(")
356              (setq special ?\()
357              )
358             (t
359              (setq special nil)
360              (setq dest (concat dest str))
361              ))
362       (setq column (nth 1 ret)
363             rwl (nth 2 ret))
364       )
365     (list dest column)
366     ))
367
368 (defun tm-eword::encode-string (column str &optional mode)
369   (tm-eword::encode-rwl column (tm-eword::split-string str mode))
370   )
371
372
373 ;;; @ converter
374 ;;;
375
376 (defun tm-eword::phrase-to-rwl (phrase)
377   (let (token type dest str)
378     (while phrase
379       (setq token (car phrase))
380       (setq type (car token))
381       (cond ((eq type 'quoted-string)
382              (setq str (concat "\"" (cdr token) "\""))
383              (setq dest
384                    (append dest
385                            (list
386                             (let ((ret (tm-eword::find-charset-rule
387                                         (find-charset-string str))))
388                               (tm-eword::make-rword
389                                str (car ret)(nth 1 ret) 'phrase)
390                               )
391                             )))
392              )
393             ((eq type 'comment)
394              (setq dest
395                    (append dest
396                            '(("(" nil nil))
397                            (tm-eword::words-to-ruled-words
398                             (tm-eword::lc-words-to-words
399                              (tm-eword::split-to-lc-words (cdr token)))
400                             'comment)
401                            '((")" nil nil))
402                            ))
403              )
404             (t
405              (setq dest (append dest
406                                 (tm-eword::words-to-ruled-words
407                                  (tm-eword::lc-words-to-words
408                                   (tm-eword::split-to-lc-words (cdr token))
409                                   ) 'phrase)))
410              ))
411       (setq phrase (cdr phrase))
412       )
413     (tm-eword::space-process dest)
414     ))
415
416 (defun tm-eword::phrase-route-addr-to-rwl (phrase-route-addr)
417   (if (eq (car phrase-route-addr) 'phrase-route-addr)
418       (let ((phrase (nth 1 phrase-route-addr))
419             (route (nth 2 phrase-route-addr))
420             dest)
421         (if (eq (car (car phrase)) 'spaces)
422             (setq phrase (cdr phrase))
423           )
424         (setq dest (tm-eword::phrase-to-rwl phrase))
425         (if dest
426             (setq dest (append dest '((" " nil nil))))
427           )
428         (append
429          dest
430          (list (list (concat "<" (rfc822/addr-to-string route) ">") nil nil))
431          ))))
432
433 (defun tm-eword::addr-spec-to-rwl (addr-spec)
434   (if (eq (car addr-spec) 'addr-spec)
435       (list (list (rfc822/addr-to-string (cdr addr-spec)) nil nil))
436     ))
437
438 (defun tm-eword::mailbox-to-rwl (mbox)
439   (let ((addr (nth 1 mbox))
440         (comment (nth 2 mbox))
441         dest)
442     (setq dest (or (tm-eword::phrase-route-addr-to-rwl addr)
443                    (tm-eword::addr-spec-to-rwl addr)
444                    ))
445     (if comment
446         (setq dest
447               (append dest
448                       '((" " nil nil)
449                         ("(" nil nil))
450                       (tm-eword::split-string comment 'comment)
451                       '((")" nil nil))
452                       )))
453     dest))
454
455 (defun tm-eword::addresses-to-rwl (addresses)
456   (let ((dest (tm-eword::mailbox-to-rwl (car addresses))))
457     (if dest
458         (while (setq addresses (cdr addresses))
459           (setq dest (append dest
460                              '(("," nil nil))
461                              '((" " nil nil))
462                              (tm-eword::mailbox-to-rwl (car addresses))
463                              ))
464           ))
465     dest))
466
467 (defun tm-eword::encode-address-list (column str)
468   (tm-eword::encode-rwl
469    column
470    (tm-eword::addresses-to-rwl
471     (rfc822/parse-addresses
472      (rfc822/lexical-analyze str)))))
473
474
475 ;;; @ application interfaces
476 ;;;
477
478 (defun mime/encode-field (str)
479   (setq str (rfc822/unfolding-string str))
480   (let ((ret (string-match rfc822/field-top-regexp str)))
481     (or (if ret
482             (let ((field-name (substring str 0 (match-end 1)))
483                   (field-body (eliminate-top-spaces
484                                (substring str (match-end 0))))
485                   fname)
486               (if (setq ret
487                         (cond ((string= field-body "") "")
488                               ((member (setq fname (downcase field-name))
489                                        '("reply-to" "from" "sender"
490                                          "resent-reply-to" "resent-from"
491                                          "resent-sender" "to" "resent-to"
492                                          "cc" "resent-cc"
493                                          "bcc" "resent-bcc" "dcc")
494                                        )
495                                (car (tm-eword::encode-address-list
496                                      (+ (length field-name) 2) field-body))
497                                )
498                               (t
499                                (catch 'tag
500                                  (let ((r mime/no-encoding-header-fields)
501                                        fn)
502                                    (while r
503                                      (setq fn (car r))
504                                      (if (string= (downcase fn) fname)
505                                          (throw 'tag field-body)
506                                        )
507                                      (setq r (cdr r))
508                                      ))
509                                  (car (tm-eword::encode-string
510                                        (+ (length field-name) 1)
511                                        field-body 'text))
512                                  ))
513                               ))
514                   (concat field-name ": " ret)
515                 )))
516         (car (tm-eword::encode-string 0 str))
517         )))
518
519 (defun mime/exist-encoded-word-in-subject ()
520   (let ((str (rfc822/get-field-body "Subject")))
521     (if (and str (string-match mime/encoded-word-regexp str))
522         str)))
523
524 (defun mime/encode-message-header ()
525   (interactive "*")
526   (save-excursion
527     (save-restriction
528       (narrow-to-region (goto-char (point-min))
529                         (if (re-search-forward
530                              (concat
531                               "^" (regexp-quote mail-header-separator) "$")
532                              nil t)
533                             (match-beginning 0)
534                           (point-max)))
535       (goto-char (point-min))
536       (let (beg end field)
537         (while (re-search-forward rfc822/field-top-regexp nil t)
538           (setq beg (match-beginning 0))
539           (setq end (rfc822/field-end))
540           (if (and (find-charset-region beg end)
541                    (setq field
542                          (mime/encode-field
543                           (buffer-substring-no-properties beg end)
544                           ))
545                    )
546               (progn
547                 (delete-region beg end)
548                 (insert field)
549                 ))
550           ))
551       (if mime/use-X-Nsubject
552           (let ((str (mime/exist-encoded-word-in-subject)))
553             (if str
554                 (insert
555                  (concat
556                   "\nX-Nsubject: "
557                   (mime-eword/decode-string (rfc822/unfolding-string str))
558                   )))))
559       )))
560
561 (defun mime-eword/encode-string (str &optional column mode)
562   (car (tm-eword::encode-rwl (or column 0) (tm-eword::split-string str mode)))
563   )
564
565
566 ;;; @ end
567 ;;;
568
569 (provide 'tm-ew-e)
570
571 ;;; tm-ew-e.el ends here