;;; std11.el --- STD 11 functions for GNU Emacs ;; Copyright (C) 1995,96,97,98,99,2000,01,02 Free Software Foundation, Inc. ;; Author: MORIOKA Tomohiko ;; Keywords: mail, news, RFC 822, STD 11 ;; This file is part of FLIM (Faithful Library about Internet Message). ;; This program is free software; you can redistribute it and/or ;; modify it under the terms of the GNU General Public License as ;; published by the Free Software Foundation; either version 2, or (at ;; your option) any later version. ;; This program is distributed in the hope that it will be useful, but ;; WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ;; General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with GNU Emacs; see the file COPYING. If not, write to the ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, ;; Boston, MA 02110-1301, USA. ;;; Code: (require 'custom) ; std11-lexical-analyzer ;;; @ fetch ;;; (defconst std11-field-name-regexp "[!-9;-~]+") (defconst std11-field-head-regexp (concat "^" std11-field-name-regexp ":")) (defconst std11-next-field-head-regexp (concat "\n" std11-field-name-regexp ":")) (defun std11-field-end (&optional bound) "Move to end of field and return this point. The optional argument BOUNDs the search; it is a buffer position." (if (re-search-forward std11-next-field-head-regexp bound t) (goto-char (match-beginning 0)) (if (re-search-forward "^$" bound t) (goto-char (1- (match-beginning 0))) (end-of-line) (point)))) ;;;###autoload (defun std11-fetch-field (name) "Return the value of the header field NAME. The buffer is expected to be narrowed to just the headers of the message." (save-excursion (goto-char (point-min)) (let ((case-fold-search t)) (if (re-search-forward (concat "^" name ":[ \t]*") nil t) (buffer-substring-no-properties (match-end 0) (std11-field-end)) )))) ;;;###autoload (defun std11-narrow-to-header (&optional boundary) "Narrow to the message header. If BOUNDARY is not nil, it is used as message header separator." (narrow-to-region (goto-char (point-min)) (if (re-search-forward (concat "^\\(" (regexp-quote (or boundary "")) "\\)?$") nil t) (match-beginning 0) (point-max) ))) ;;;###autoload (defun std11-field-body (name &optional boundary) "Return the value of the header field NAME. If BOUNDARY is not nil, it is used as message header separator." (save-excursion (save-restriction (inline (std11-narrow-to-header boundary) (std11-fetch-field name)) ))) (defun std11-find-field-body (field-names &optional boundary) "Return the first found field-body specified by FIELD-NAMES of the message header in current buffer. If BOUNDARY is not nil, it is used as message header separator." (save-excursion (save-restriction (std11-narrow-to-header boundary) (let ((case-fold-search t) field-name) (catch 'tag (while (setq field-name (car field-names)) (goto-char (point-min)) (if (re-search-forward (concat "^" field-name ":[ \t]*") nil t) (throw 'tag (buffer-substring-no-properties (match-end 0) (std11-field-end))) ) (setq field-names (cdr field-names)) )))))) (defun std11-field-bodies (field-names &optional default-value boundary) "Return list of each field-bodies of FIELD-NAMES of the message header in current buffer. If BOUNDARY is not nil, it is used as message header separator." (save-excursion (save-restriction (std11-narrow-to-header boundary) (let* ((case-fold-search t) (dest (make-list (length field-names) default-value)) (s-rest field-names) (d-rest dest) field-name) (while (setq field-name (car s-rest)) (goto-char (point-min)) (if (re-search-forward (concat "^" field-name ":[ \t]*") nil t) (setcar d-rest (buffer-substring-no-properties (match-end 0) (std11-field-end))) ) (setq s-rest (cdr s-rest) d-rest (cdr d-rest)) ) dest)))) (defun std11-header-string (regexp &optional boundary) "Return string of message header fields matched by REGEXP. If BOUNDARY is not nil, it is used as message header separator." (let ((case-fold-search t)) (save-excursion (save-restriction (std11-narrow-to-header boundary) (goto-char (point-min)) (let (field header) (while (re-search-forward std11-field-head-regexp nil t) (setq field (buffer-substring (match-beginning 0) (std11-field-end))) (if (string-match regexp field) (setq header (concat header field "\n")) )) header) )))) (defun std11-header-string-except (regexp &optional boundary) "Return string of message header fields not matched by REGEXP. If BOUNDARY is not nil, it is used as message header separator." (let ((case-fold-search t)) (save-excursion (save-restriction (std11-narrow-to-header boundary) (goto-char (point-min)) (let (field header) (while (re-search-forward std11-field-head-regexp nil t) (setq field (buffer-substring (match-beginning 0) (std11-field-end))) (if (not (string-match regexp field)) (setq header (concat header field "\n")) )) header) )))) (defun std11-collect-field-names (&optional boundary) "Return list of all field-names of the message header in current buffer. If BOUNDARY is not nil, it is used as message header separator." (save-excursion (save-restriction (std11-narrow-to-header boundary) (goto-char (point-min)) (let (dest name) (while (re-search-forward std11-field-head-regexp nil t) (setq name (buffer-substring-no-properties (match-beginning 0)(1- (match-end 0)))) (or (member name dest) (setq dest (cons name dest)) ) ) dest)))) ;;; @ unfolding ;;; ;;;###autoload (defun std11-unfold-string (string) "Unfold STRING as message header field." (let ((dest "") (p 0)) (while (string-match "\n\\([ \t]\\)" string p) (setq dest (concat dest (substring string p (match-beginning 0)) (substring string (match-beginning 1) (setq p (match-end 0))) )) ) (concat dest (substring string p)) )) ;;; @ quoted-string ;;; (defun std11-wrap-as-quoted-pairs (string specials) (let (dest (i 0) (b 0) (len (length string)) ) (while (< i len) (let ((chr (aref string i))) (if (memq chr specials) (setq dest (concat dest (substring string b i) "\\") b i) )) (setq i (1+ i)) ) (concat dest (substring string b)) )) (defconst std11-non-qtext-char-list '(?\" ?\\ ?\r ?\n)) (defun std11-wrap-as-quoted-string (string) "Wrap STRING as RFC 822 quoted-string." (concat "\"" (std11-wrap-as-quoted-pairs string std11-non-qtext-char-list) "\"")) (defun std11-strip-quoted-pair (string) "Strip quoted-pairs in STRING." (let (dest (b 0) (i 0) (len (length string)) ) (while (< i len) (let ((chr (aref string i))) (if (eq chr ?\\) (setq dest (concat dest (substring string b i)) b (1+ i) i (+ i 2)) (setq i (1+ i)) ))) (concat dest (substring string b)) )) (defun std11-strip-quoted-string (string) "Strip quoted-string STRING." (let ((len (length string))) (or (and (>= len 2) (let ((max (1- len))) (and (eq (aref string 0) ?\") (eq (aref string max) ?\") (std11-strip-quoted-pair (substring string 1 max)) ))) string))) ;;; @ lexical analyze ;;; (defcustom std11-lexical-analyzer '(std11-analyze-quoted-string std11-analyze-domain-literal std11-analyze-comment std11-analyze-spaces std11-analyze-special std11-analyze-atom) "*List of functions to return result of lexical analyze. Each function must have two arguments: STRING and START. STRING is the target string to be analyzed. START is start position of STRING to analyze. Previous function is preferred to next function. If a function returns nil, next function is used. Otherwise the return value will be the result." :group 'news :group 'mail :type '(repeat function)) (eval-and-compile (defconst std11-space-char-list '(? ?\t ?\n)) (defconst std11-special-char-list '(?\] ?\[ ?\( ?\) ?< ?> ?@ ?, ?\; ?: ?\\ ?\" ?.)) ) ;; (defconst std11-spaces-regexp ;; (eval-when-compile (concat "[" std11-space-char-list "]+"))) (defconst std11-non-atom-regexp (eval-when-compile (concat "[" std11-special-char-list std11-space-char-list "]"))) (defconst std11-atom-regexp (eval-when-compile (concat "[^" std11-special-char-list std11-space-char-list "]+"))) (defun std11-analyze-spaces (string start) (if (and (string-match (eval-when-compile (concat "[" std11-space-char-list "]+")) string start) (= (match-beginning 0) start)) (let ((end (match-end 0))) (cons (cons 'spaces (substring string start end)) ;;(substring string end) end) ))) (defun std11-analyze-special (string start) (if (and (> (length string) start) (memq (aref string start) std11-special-char-list)) (cons (cons 'specials (substring string start (1+ start))) ;;(substring string 1) (1+ start)) )) (defun std11-analyze-atom (string start) (if (string-match std11-non-atom-regexp string start) (if (> (match-beginning 0) start) (cons (cons 'atom (substring string start (match-beginning 0))) (match-beginning 0)) nil) (cons (cons 'atom (substring string start)) (length string))) ;; (if (and (string-match std11-atom-regexp string start) ;; (= (match-beginning 0) start)) ;; (let ((end (match-end 0))) ;; (cons (cons 'atom (substring string start end)) ;; ;;(substring string end) ;; end) ;; )) ) (defun std11-check-enclosure (string open close &optional recursive from) (let ((len (length string)) (i (or from 0)) ) (if (and (> len i) (eq (aref string i) open)) (let (p chr) (setq i (1+ i)) (catch 'tag (while (< i len) (setq chr (aref string i)) (cond ((eq chr ?\\) (setq i (1+ i)) (if (>= i len) (throw 'tag nil) ) (setq i (1+ i)) ) ((eq chr close) (throw 'tag (1+ i)) ) ((eq chr open) (if (and recursive (setq p (std11-check-enclosure string open close recursive i)) ) (setq i p) (throw 'tag nil) )) (t (setq i (1+ i)) )) )))))) (defun std11-analyze-quoted-string (string start) (let ((p (std11-check-enclosure string ?\" ?\" nil start))) (if p (cons (cons 'quoted-string (substring string (1+ start) (1- p))) ;;(substring string p)) p) ))) (defun std11-analyze-domain-literal (string start) (let ((p (std11-check-enclosure string ?\[ ?\] nil start))) (if p (cons (cons 'domain-literal (substring string (1+ start) (1- p))) ;;(substring string p)) p) ))) (defun std11-analyze-comment (string start) (let ((p (std11-check-enclosure string ?\( ?\) t start))) (if p (cons (cons 'comment (substring string (1+ start) (1- p))) ;;(substring string p)) p) ))) ;;;###autoload (defun std11-lexical-analyze (string &optional analyzer start) "Analyze STRING as lexical tokens of STD 11." (or analyzer (setq analyzer std11-lexical-analyzer)) (or start (setq start 0)) (let ((len (length string)) dest ret) (while (< start len) (setq ret (let ((rest analyzer) func r) (while (and (setq func (car rest)) (null (setq r (funcall func string start)))) (setq rest (cdr rest))) (or r (cons (cons 'error (substring string start)) (1+ len))) )) (setq dest (cons (car ret) dest) start (cdr ret)) ) (nreverse dest) )) ;;; @ parser ;;; (defun std11-ignored-token-p (token) (let ((type (car token))) (or (eq type 'spaces)(eq type 'comment)) )) (defun std11-parse-token (lal) (let (token itl) (while (and lal (progn (setq token (car lal)) (std11-ignored-token-p token) )) (setq lal (cdr lal)) (setq itl (cons token itl)) ) (cons (nreverse (cons token itl)) (cdr lal)) )) (defun std11-parse-ascii-token (lal) (let (token itl parsed token-value) (while (and lal (setq token (car lal)) (or (std11-ignored-token-p token) (if (and (setq token-value (cdr token)) (delq 'ascii (find-charset-string token-value))) (setq token nil) ))) (setq lal (cdr lal)) (setq itl (cons token itl)) ) (if (and token (setq parsed (nreverse (cons token itl))) ) (cons parsed (cdr lal)) ))) (defun std11-parse-token-or-comment (lal) (let (token itl) (while (and lal (progn (setq token (car lal)) (eq (car token) 'spaces) )) (setq lal (cdr lal)) (setq itl (cons token itl)) ) (cons (nreverse (cons token itl)) (cdr lal)) )) (defun std11-parse-word (lal) (let ((ret (std11-parse-ascii-token lal))) (if ret (let ((elt (car ret)) (rest (cdr ret)) ) (if (or (assq 'atom elt) (assq 'quoted-string elt)) (cons (cons 'word elt) rest) ))))) (defun std11-parse-word-or-comment-or-period (lal) (let ((ret (std11-parse-token-or-comment lal))) (if ret (let ((elt (car ret)) (rest (cdr ret)) ) (cond ((or (assq 'atom elt) (assq 'quoted-string elt)) (cons (cons 'word elt) rest) ) ((assq 'comment elt) (cons (cons 'comment-word elt) rest) ) ((string-equal (cdr (assq 'specials elt)) ".") (cons (cons 'period elt) rest) )) )))) (defun std11-parse-phrase (lal) (let (ret phrase) (while (setq ret (std11-parse-word-or-comment-or-period lal)) (setq phrase (append phrase (cdr (car ret)))) (setq lal (cdr ret)) ) (if phrase (cons (cons 'phrase phrase) lal) ))) (defun std11-parse-local-part (lal) (let ((ret (std11-parse-word lal))) (if ret (let ((local-part (cdr (car ret))) dot) (setq lal (cdr ret)) (while (and (setq ret (std11-parse-ascii-token lal)) (setq dot (car ret)) (string-equal (cdr (assq 'specials dot)) ".") (setq ret (std11-parse-word (cdr ret))) (setq local-part (append local-part dot (cdr (car ret))) ) (setq lal (cdr ret)) )) (cons (cons 'local-part local-part) lal) )))) (defun std11-parse-sub-domain (lal) (let ((ret (std11-parse-ascii-token lal))) (if ret (let ((sub-domain (car ret))) (if (or (assq 'atom sub-domain) (assq 'domain-literal sub-domain) ) (cons (cons 'sub-domain sub-domain) (cdr ret) ) ))))) (defun std11-parse-domain (lal) (let ((ret (std11-parse-sub-domain lal))) (if ret (let ((domain (cdr (car ret))) dot) (setq lal (cdr ret)) (while (and (setq ret (std11-parse-ascii-token lal)) (setq dot (car ret)) (string-equal (cdr (assq 'specials dot)) ".") (setq ret (std11-parse-sub-domain (cdr ret))) (setq domain (append domain dot (cdr (car ret))) ) (setq lal (cdr ret)) )) (cons (cons 'domain domain) lal) )))) (defun std11-parse-at-domain (lal) (let ((ret (std11-parse-ascii-token lal)) at-sign) (if (and ret (setq at-sign (car ret)) (string-equal (cdr (assq 'specials at-sign)) "@") (setq ret (std11-parse-domain (cdr ret))) ) (cons (cons 'at-domain (append at-sign (cdr (car ret)))) (cdr ret)) ))) (defun std11-parse-addr-spec (lal) (let ((ret (std11-parse-local-part lal)) addr) (if (and ret (prog1 (setq addr (cdr (car ret))) (setq lal (cdr ret)) (and (setq ret (std11-parse-at-domain lal)) (setq addr (append addr (cdr (car ret)))) (setq lal (cdr ret)) ))) (cons (cons 'addr-spec addr) lal) ))) (defun std11-parse-route (lal) (let ((ret (std11-parse-at-domain lal)) route comma colon) (if (and ret (progn (setq route (cdr (car ret))) (setq lal (cdr ret)) (while (and (setq ret (std11-parse-ascii-token lal)) (setq comma (car ret)) (string-equal (cdr (assq 'specials comma)) ",") (setq ret (std11-parse-at-domain (cdr ret))) ) (setq route (append route comma (cdr (car ret)))) (setq lal (cdr ret)) ) (and (setq ret (std11-parse-ascii-token lal)) (setq colon (car ret)) (string-equal (cdr (assq 'specials colon)) ":") (setq route (append route colon)) ) )) (cons (cons 'route route) (cdr ret) ) ))) (defun std11-parse-route-addr (lal) (let ((ret (std11-parse-ascii-token lal)) < route addr-spec >) (if (and ret (setq < (car ret)) (string-equal (cdr (assq 'specials <)) "<") (setq lal (cdr ret)) (progn (and (setq ret (std11-parse-route lal)) (setq route (cdr (car ret))) (setq lal (cdr ret)) ) (setq ret (std11-parse-addr-spec lal)) ) (setq addr-spec (cdr (car ret))) (setq lal (cdr ret)) (setq ret (std11-parse-ascii-token lal)) (setq > (car ret)) (string-equal (cdr (assq 'specials >)) ">") ) (cons (cons 'route-addr (append route addr-spec)) (cdr ret) ) ))) (defun std11-parse-phrase-route-addr (lal) (let ((ret (std11-parse-phrase lal)) phrase) (if ret (progn (setq phrase (cdr (car ret))) (setq lal (cdr ret)) )) (if (setq ret (std11-parse-route-addr lal)) (cons (list 'phrase-route-addr phrase (cdr (car ret))) (cdr ret)) ))) (defun std11-parse-mailbox (lal) (let ((ret (or (std11-parse-phrase-route-addr lal) (std11-parse-addr-spec lal))) mbox comment) (if (and ret (prog1 (setq mbox (car ret)) (setq lal (cdr ret)) (if (and (setq ret (std11-parse-token-or-comment lal)) (setq comment (cdr (assq 'comment (car ret)))) ) (setq lal (cdr ret)) ))) (cons (list 'mailbox mbox comment) lal) ))) (defun std11-parse-group (lal) (let ((ret (std11-parse-phrase lal)) phrase colon comma mbox semicolon) (if (and ret (setq phrase (cdr (car ret))) (setq lal (cdr ret)) (setq ret (std11-parse-ascii-token lal)) (setq colon (car ret)) (string-equal (cdr (assq 'specials colon)) ":") (setq lal (cdr ret)) (progn (and (setq ret (std11-parse-mailbox lal)) (setq mbox (list (car ret))) (setq lal (cdr ret)) (progn (while (and (setq ret (std11-parse-ascii-token lal)) (setq comma (car ret)) (string-equal (cdr (assq 'specials comma)) ",") (setq lal (cdr ret)) (setq ret (std11-parse-mailbox lal)) (setq mbox (cons (car ret) mbox)) (setq lal (cdr ret)) ) ))) (and (setq ret (std11-parse-ascii-token lal)) (setq semicolon (car ret)) (string-equal (cdr (assq 'specials semicolon)) ";") ))) (cons (list 'group phrase (nreverse mbox)) (cdr ret) ) ))) (defun std11-parse-address (lal) (or (std11-parse-group lal) (std11-parse-mailbox lal) )) (defun std11-parse-addresses (lal) (let ((ret (std11-parse-address lal))) (if ret (let ((dest (list (car ret)))) (setq lal (cdr ret)) (while (and (setq ret (std11-parse-ascii-token lal)) (string-equal (cdr (assq 'specials (car ret))) ",") (setq ret (std11-parse-address (cdr ret))) ) (setq dest (cons (car ret) dest)) (setq lal (cdr ret)) ) (nreverse dest) )))) (defun std11-parse-msg-id (lal) (let ((ret (std11-parse-ascii-token lal)) < addr-spec >) (if (and ret (setq < (car ret)) (string-equal (cdr (assq 'specials <)) "<") (setq lal (cdr ret)) (setq ret (std11-parse-addr-spec lal)) (setq addr-spec (car ret)) (setq lal (cdr ret)) (setq ret (std11-parse-ascii-token lal)) (setq > (car ret)) (string-equal (cdr (assq 'specials >)) ">") ) (cons (cons 'msg-id (cdr addr-spec)) (cdr ret)) ))) (defun std11-parse-msg-ids (tokens) "Parse lexical TOKENS as `*(phrase / msg-id)', and return the result." (let ((ret (or (std11-parse-msg-id tokens) (std11-parse-phrase tokens)))) (if ret (let ((dest (list (car ret)))) (setq tokens (cdr ret)) (while (setq ret (or (std11-parse-msg-id tokens) (std11-parse-phrase tokens))) (setq dest (cons (car ret) dest)) (setq tokens (cdr ret)) ) (nreverse dest) )))) (defalias 'std11-parse-in-reply-to 'std11-parse-msg-ids) (make-obsolete 'std11-parse-in-reply-to 'std11-parse-msg-ids) ;;; @ composer ;;; (defun std11-addr-to-string (seq) "Return string from lexical analyzed list SEQ represents addr-spec of RFC 822." (mapconcat (function (lambda (token) (let ((name (car token))) (cond ((eq name 'spaces) "") ((eq name 'comment) "") ((eq name 'quoted-string) (concat "\"" (cdr token) "\"")) ((eq name 'domain-literal) (concat "[" (cdr token) "]")) (t (cdr token))) ))) seq "") ) ;;;###autoload (defun std11-address-string (address) "Return string of address part from parsed ADDRESS of RFC 822." (cond ((eq (car address) 'group) (mapconcat (function std11-address-string) (nth 2 address) ", ") ) ((eq (car address) 'mailbox) (let ((addr (nth 1 address))) (std11-addr-to-string (if (eq (car addr) 'phrase-route-addr) (nth 2 addr) (cdr addr) ) ))))) (defun std11-comment-value-to-string (value) (if (stringp value) (std11-strip-quoted-pair value) (let ((dest "")) (while value (setq dest (concat dest (if (stringp (car value)) (car value) (concat "(" (std11-comment-value-to-string (cdr (car value))) ")") )) value (cdr value)) ) dest))) ;;;###autoload (defun std11-full-name-string (address) "Return string of full-name part from parsed ADDRESS of RFC 822." (cond ((eq (car address) 'group) (mapconcat (function (lambda (token) (cdr token) )) (nth 1 address) "") ) ((eq (car address) 'mailbox) (let ((addr (nth 1 address)) (comment (nth 2 address)) phrase) (if (eq (car addr) 'phrase-route-addr) (setq phrase (mapconcat (function (lambda (token) (let ((type (car token))) (cond ((eq type 'quoted-string) (std11-strip-quoted-pair (cdr token)) ) ((eq type 'comment) (concat "(" (std11-comment-value-to-string (cdr token)) ")") ) (t (cdr token) ))))) (nth 1 addr) "")) ) (cond ((> (length phrase) 0) phrase) (comment (std11-comment-value-to-string comment)) ) )))) ;;;###autoload (defun std11-msg-id-string (msg-id) "Return string from parsed MSG-ID of RFC 822." (concat "<" (std11-addr-to-string (cdr msg-id)) ">") ) ;;;###autoload (defun std11-fill-msg-id-list-string (string &optional column) "Fill list of msg-id in STRING, and return the result." (or column (setq column 12)) (let ((lal (std11-lexical-analyze string)) dest) (let ((ret (std11-parse-msg-id lal))) (if ret (let* ((str (std11-msg-id-string (car ret))) (len (length str))) (setq lal (cdr ret)) (if (> (+ len column) 76) (setq dest (concat dest "\n " str) column (1+ len)) (setq dest str column (+ column len)) )) (setq dest (concat dest (cdr (car lal))) lal (cdr lal)) )) (while lal (let ((ret (std11-parse-msg-id lal))) (if ret (let* ((str (std11-msg-id-string (car ret))) (len (1+ (length str)))) (setq lal (cdr ret)) (if (> (+ len column) 76) (setq dest (concat dest "\n " str) column len) (setq dest (concat dest " " str) column (+ column len)) )) (setq dest (concat dest (cdr (car lal))) lal (cdr lal)) ))) dest)) ;;; @ parser with lexical analyzer ;;; ;;;###autoload (defun std11-parse-address-string (string) "Parse STRING as mail address." (std11-parse-address (std11-lexical-analyze string)) ) ;;;###autoload (defun std11-parse-addresses-string (string) "Parse STRING as mail address list." (std11-parse-addresses (std11-lexical-analyze string)) ) ;;;###autoload (defun std11-parse-msg-id-string (string) "Parse STRING as msg-id." (std11-parse-msg-id (std11-lexical-analyze string)) ) ;;;###autoload (defun std11-parse-msg-ids-string (string) "Parse STRING as `*(phrase / msg-id)'." (std11-parse-msg-ids (std11-lexical-analyze string)) ) ;;;###autoload (defun std11-extract-address-components (string) "Extract full name and canonical address from STRING. Returns a list of the form (FULL-NAME CANONICAL-ADDRESS). If no name can be extracted, FULL-NAME will be nil." (let* ((structure (car (std11-parse-address-string (std11-unfold-string string)))) (phrase (std11-full-name-string structure)) (address (std11-address-string structure)) ) (list phrase address) )) ;;; @ end ;;; (provide 'std11) ;;; std11.el ends here