1 ;;; tinylibmail.el --- Library of mail related functions
3 ;; This file is not part of Emacs
7 ;; Copyright (C) 1995-2007 Jari Aalto
8 ;; Keywords: extensions
10 ;; Maintainer: Jari Aalto
12 ;; To get information on this program, call M-x tinylibmail-version.
13 ;; Look at the code with folding.el
17 ;; This program is free software; you can redistribute it and/or modify it
18 ;; under the terms of the GNU General Public License as published by the Free
19 ;; Software Foundation; either version 2 of the License, or (at your option)
22 ;; This program is distributed in the hope that it will be useful, but
23 ;; WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
24 ;; or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
27 ;; You should have received a copy of the GNU General Public License
28 ;; along with program; see the file COPYING. If not, write to the
29 ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
30 ;; Boston, MA 02110-1301, USA.
32 ;; Visit <http://www.gnu.org/copyleft/gpl.html> for more information
39 ;; ........................................................ &t-install ...
40 ;; Put this file on your Emacs-Lisp load path, add following into your
41 ;; ~/.emacs startup file
43 ;; (require 'tinylibm)
45 ;; No, there is no mistake here. The 'm' lib contains all autoloads
54 ;; o This is library. Package itself does nothing.
55 ;; o Collection of functions to deal with Mail/News specific tasks.
63 ;;{{{ setup: -- require
65 ;;; ....................................................... &v-require ...
68 (require 'sendmail) ;; mail-header-separator
71 (defvar mail-abbrevs) ;Silence ByteCompiler
73 (defvar rmail-current-message nil)
76 (autoload 'build-mail-aliases "mail-abbrevs"))
78 (autoload 'mail-abbrevs-setup "mailabbrev")
79 (autoload 'build-mail-aliases "mailalias")
80 (autoload 'build-mail-abbrevs "mailabbrev")))
81 (autoload 'rmail-msgbeg "rmail")
82 (autoload 'rmail-msgend "rmail")
83 (autoload 'gnus-group-get-parameter "gnus"))
86 (ti::package-use-dynamic-compilation))
89 ;;{{{ setup: -- private
91 ;;; ......................................................... &v-hooks ...
93 (defvar ti:mail-load-hook nil
94 "Hook that is run when package is loaded.")
96 ;;; ....................................................... &v-private ...
98 (defvar ti:mail-ret nil
99 "Global return value of this package.")
101 (defvar ti:mail-mail-buffer " *ti::mail-mail*"
102 "*Temporary mail buffer name.")
104 ;; Variables could be modified. defsubst makes them persistent
106 (defsubst ti::mail-pgp-signature-begin-line ()
107 "Signature start line."
108 "-----BEGIN PGP SIGNATURE-----")
110 (defsubst ti::mail-pgp-signature-end-line ()
111 "Signature end line."
112 "-----END PGP SIGNATURE-----")
114 ;; Signed message has:
116 ;; -----BEGIN PGP SIGNED MESSAGE-----
117 ;; -----BEGIN PGP SIGNATURE-----
118 ;; -----END PGP SIGNATURE-----
120 (defsubst ti::mail-pgp-signed-begin-line ()
121 "Text for start of PGP signed messages."
122 "-----BEGIN PGP SIGNED MESSAGE-----")
124 (defsubst ti::mail-pgp-signed-end-line ()
125 "Text for start of PGP signed messages."
126 (ti::mail-pgp-signature-end-line))
128 (defsubst ti::mail-pgp-pkey-begin-line ()
129 "PGP public key begin line."
130 "-----BEGIN PGP PUBLIC KEY BLOCK-----")
132 (defsubst ti::mail-pgp-pkey-end-line ()
133 "PGP public key end line."
134 "-----END PGP PUBLIC KEY BLOCK-----")
136 (defsubst ti::mail-pgp-msg-begin-line ()
137 "PGP message, typically base64 signed, begin line."
138 "-----BEGIN PGP MESSAGE-----")
140 (defsubst ti::mail-pgp-msg-end-line ()
141 "PGP message, typically base64 signed, end line."
142 "-----END PGP MESSAGE-----")
144 (defsubst ti::mail-pgp-any-pgp-line-regexp (&optional anchor-left)
145 "Return regexp that match any pgp TAG.
146 If ANCHOR-LEFT is non-nil; the regexp will contains left ^ anchor."
147 ;; The lines can be broken, when there is encrypted/signed message
148 ;; NOTE: there is no anchor by default; because sometimes use may have
149 ;; indented the whole PGP block (e.g. in his web page or in .doc file)
152 (if anchor-left "^" "")
153 "- -----\\(BEGIN\\|END\\) PGP.*-----"
155 (if anchor-left "^" "")
156 "-----\\(BEGIN\\|END\\) PGP.*-----"))
158 ;;; ----------------------------------------------------------------------
160 (defsubst ti::mail-ip-raw-p (ip)
161 "Check raw nnn.nnn.nnn.nnn IP."
162 (string-match "^[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+$" ip))
164 ;;; ----------------------------------------------------------------------
166 (defsubst ti::mail-ip-top-level-domain (host)
167 "Convert HOST a.b.c => b.c domain.
168 If HOST is raw numeric IP, do nothing."
170 ((ti::mail-ip-raw-p host)
172 ((or (string-match "\\.\\([^.]+\\.[^.]+\\)$" host)
173 (string-match "^\\([^.]+\\.[^.]+\\)$" host))
174 (match-string 1 host))))
176 ;;; ----------------------------------------------------------------------
178 (defsubst ti::mail-ip-3-level-domain (host)
179 "Convert HOST a.b.c.d => b.c.d domain."
180 (when (string-match "\\.\\([^.]+\\.[^.]+\\.[^.]+\\)$" host)
181 (match-string 1 host)))
183 ;;; ----------------------------------------------------------------------
185 (defsubst ti::mail-ip-cleanup (word)
186 "Clean WORD to format 'aa.bb.cc'. Remove offending characters.
187 Remove all characters up till @: this@email.com => email.com
188 Remove all not(alphanumeric, dash, dot) charcters.
190 For example a word at point may include anything:
192 <bb.com> \"bb.com\" this@bb.com
194 All of the above will become:
198 (replace-regexp-in-string
200 (replace-regexp-in-string "^.*@" "" word))))
202 ;;; ----------------------------------------------------------------------
204 (defun ti::mail-ip-at-point-1 ()
205 "Read only word containing characters [-.a-zA-z0-9]."
208 ;; depending where the point is, from this word different part
209 ;; is read: foo.com[1.2.3.4]
213 (skip-chars-backward "-.a-zA-Z0-9")
215 (skip-chars-forward "-.a-zA-Z0-9")
216 (unless (eq beg (point))
217 (buffer-substring beg (point))))))
219 ;;; ----------------------------------------------------------------------
221 (defun ti::mail-ip-at-point ()
222 "Read domain ip IP name at point."
223 (let* ((word (ti::mail-ip-at-point-1)))
224 (when (not (ti::nil-p word))
226 (setq word (ti::mail-ip-cleanup word))
227 (if (ti::mail-ip-raw-p word)
229 (ti::mail-ip-top-level-domain word)))))
231 ;;; ----------------------------------------------------------------------
233 (defsubst ti::mail-news-group () ;ding & gnus compatible
234 "Return newsgroup name if group exists."
235 (if (boundp 'gnus-newsgroup-name)
236 (symbol-value 'gnus-newsgroup-name)))
238 ;;; ........................................................ &v-public ...
239 ;;; User configurable -- but not recommended.
241 ;; See gnus.el or gnus-msg.el gnus-required-headers
242 ;; The 'in-reply-to is for mail messages (additional)
244 (defconst ti:mail-required-headers
245 '(from date newsgroups subject path message-id in-reply-to references)
246 "*All required fields that RFC977 and RFC1036 requires.
247 Make sure symbol names are all in lowercase.")
249 (defvar ti:mail-parse-name-not-accept
251 "[A-Z][/][A-Z]" ;company division PMR/TMS ?
252 "\\|^[A-Z]+\\'" ;all in capitals
253 "\\|^[-]$" ;single '-' word
254 "\\|[.0-9]" ;maybe phone number ?
255 "\\|com\\|org\\|edu")
256 "*Regexp to exclude non-valid people names.
257 We can't be sure that the names are really good names when we parse the
258 senders From field. Let's see an example
260 \"Person Someone p. nnn-nnn-nnn\"
262 There obviously isn't 3rd name, it's used for phone abbrev. And the last
263 word is the actual phone number.
265 This regexp tells which word matches are false name hits.
266 In this example it'd leave:
269 See `ti::mail-parse-name'")
272 ;;{{{ setup: -- version
274 (defconst tinylibmail-version (substring "$Revision: 2.68 $" 11 16)
275 "Latest version number.")
277 (defconst tinylibmail-version-id
278 "$Id: tinylibmail.el,v 2.68 2007/05/07 10:50:08 jaalto Exp $"
279 "Latest modification time and version number.")
281 ;;; ----------------------------------------------------------------------
283 (defun tinylibmail-version (&optional arg)
284 "Show version information. ARG tell to print message in echo area only."
286 (ti::package-version-info "tinylibmail.el" arg))
288 ;;; ----------------------------------------------------------------------
290 (defun tinylibmail-submit-feedback ()
291 "Submit suggestions, error corrections, impressions, anything..."
293 (ti::package-submit-feedback "tinylibmail.el"))
298 ;;; ----------------------------------------------------------------------
300 (defun ti::mail-signature-p ()
301 "Return beginning of line point if \\n-- *\\n is found.
302 The de facto, while not standardized by any RFC signature separator
303 it \\n-- \\n. The trailing whitespace is very unfortunate evolution
304 to separate signatures from message digests \\n--\\n.
306 This function accepts trailing spaces or just n\\--\\n"
307 (let* ((point (point))) ;avoid save-excursion.
309 (prog1 (if (re-search-forward "\n-- *\n" nil t)
310 (1+ (match-beginning 0)))
313 ;;; ----------------------------------------------------------------------
315 (defun ti::mail-body-empty-p ()
316 "Check if there is nothing in the body or if whole buffer is empty."
318 (ti::mail-text-start 'move)
319 (eq (point) (point-max))))
321 ;;; ----------------------------------------------------------------------
323 (defun ti::mail-body-clear ()
324 "Delete message body."
325 (ti::mail-text-start 'move)
326 (delete-region (point) (point-max)))
328 ;;; ----------------------------------------------------------------------
330 (put 'ti::mail-set-region 'lisp-indent-function 1)
331 (put 'ti::mail-set-region 'edebug-form-spec '(body))
332 (defmacro ti::mail-set-region (beg end)
333 "Set BEG END to whole buffer if they don't have value."
337 (setq (, beg) (ti::mail-text-start)))
339 (setq (, end) (point-max))))))
341 ;;; ----------------------------------------------------------------------
343 (put 'ti::mail-point-in-header-macro 'lisp-indent-function 0)
344 (put 'ti::mail-point-in-header-macro 'edebug-form-spec '(body))
345 (defmacro ti::mail-point-in-header-macro (&rest body)
346 "Run BODY only if point is inside mail header area."
348 (when (< (point) (ti::mail-text-start))
351 ;;; ----------------------------------------------------------------------
353 (defun ti::mail-message-length ()
354 "Return message's body length, not including the headers.
355 The message length is measured be counting character between the
356 BODY begin and BODY end. Any amount of whitespaces around the body is
361 The start point defaults to `point-min' if body can't be found.
363 If there is PGP signed block, then the body length is the text inside
364 PGP signed block, not the original message body.
366 Signed headers are also skipped.
368 -----BEGIN PGP SIGNED MESSAGE-----
370 ## < signed headers begin mark \\n##
371 Subject: some subject
373 < empty space ends headers
374 Hi, I swanted to tell you... < BODY BEGIN IS HERE"
375 (let* ((end (ti::mail-pgp-signed-conventional-p))
380 (null (ti::mail-pgp-signature-detached-p)))
381 ;; Do not count empty lines at the end of body
382 (goto-char end) (skip-chars-backward " \t\n") (setq end (point))
384 (re-search-forward (ti::mail-pgp-signed-begin-line))
386 ;; now we're inside BODY of text, but it's not that simple yet. User
387 ;; may have signed headers and they are inserterted into body
390 ;; -----BEGIN PGP SIGNED MESSAGE-----
393 ;; Subject: See this ma!
395 ;; Body text starts here.
397 ;; Note, there is no spaces, becasue the body is trimmed
398 (when (looking-at "\n##\n")
399 (goto-char (match-end 0))
400 (re-search-forward "^$"))
401 ;; Ignore leading spaces
402 (skip-chars-forward " \t\n")
405 (ti::mail-text-start 'move)
406 (skip-chars-forward " \t\n") (setq beg (point))
407 (goto-char (point-max))
408 (if (eq beg (point)) ;Empty buffer
410 (skip-chars-backward " \t\n") (setq end (point))
411 (- (point) beg)))))))
413 ;;; ----------------------------------------------------------------------
414 ;;; #todo: this is old function and should be removed.
416 (defun ti::mail-get-2re (re str)
417 "Use RE and match STR. Return list ('' '') if not matched."
420 (if (eq nil (string-match re str))
421 t ;do nothing, not matched
423 (setq m1 (substring str (match-beginning 1)
426 (setq m2 (substring str (match-beginning 2)
430 ;;; ----------------------------------------------------------------------
432 (defun ti::mail-required-headers ()
433 "Return standard RFC header required when posting.
437 `ti:mail-required-headers'
438 `gnus-required-headers'
442 list '(header-name-symbol .. )
443 nil gnus not loaded ?"
445 ((listp ti:mail-required-headers)
446 ti:mail-required-headers)
447 ((boundp 'gnus-required-headers)
448 (symbol-value 'gnus-required-headers))
452 ;;; ----------------------------------------------------------------------
454 (defun ti::mail-mail-mode-p ()
455 "Check if some mail MUA mode is tuned on this buffer: RMAIL, VM, MH ..."
457 "^\\(vm-\\|rmail-\\|mh-\\|gnus-article-\\|message\\).*mode"
458 (symbol-name major-mode)))
460 ;;; ----------------------------------------------------------------------
462 (defun ti::mail-mailbox-p ()
463 "Check if two first lines look like Berkeley mailbox format."
466 ;; From foo@some.com Wed Dec 19 19:19:41 2001
467 ;; Received: from some.com ([000.22.68.000])
468 (and (looking-at "^\\(From:?\\|Return-Path:\\) .*@")
471 (looking-at "^[a-zA-Z-]+:[ \t]+[^ \r\r\n]"))))
473 ;;; ----------------------------------------------------------------------
475 (defun ti::mail-mail-p ()
476 "Check if first line contain left flushed header 'Header:'.
477 This is a sign that current buffer is in mail-like.
478 You should also check the mode name to get more reliable results."
479 (or (memq major-mode '(message-mode mail-mode))
482 (looking-at "^[-A-Za-z0-9][-A-Za-z0-9]+:"))))
484 ;;; ----------------------------------------------------------------------
486 (defun ti::mail-header-area-size ()
487 "Count size of header area.
488 This function can only be called from a buffer that has
489 `mail-header-separator'. Function count the characters in the header area.
490 You can use this information to determine if the headers have been
491 changed after the last check.
496 nil can't find `mail-header-separator'"
499 (when (re-search-forward (regexp-quote mail-header-separator) nil t)
500 (- (point) (point-min)))))
502 ;;; ----------------------------------------------------------------------
503 ;;; - This is suitable for RMAIL, GNUS and for individual buffers
504 ;;; holding mail or news messages.
506 (defun ti::mail-hmax (&optional move noerr)
507 "Search max point of header, optionally MOVE and NOERR.
508 Order is: `mail-header-separator' or find all \"Headers:\" and then
509 return next line after them. The header must start at `point-min'.
511 If no point can be found, return `point-min'."
512 (let ((point (point-min)))
513 (when (ti::mail-mail-p)
516 ;; VM's "t" key shows all headers, including the
517 ;; "From xxxx"foo.com" line which is not actual header, because
518 ;; it has no colon. Skip ovber it if we see it.
519 (if (looking-at "From")
521 ;; GNUS 4 sets the mail-header-separator to "" ??
522 (if (and (not (ti::nil-p mail-header-separator))
523 (re-search-forward (regexp-quote mail-header-separator) nil t))
524 (setq point (match-beginning 0))
526 ;; Continuing line here
528 (while (and (looking-at "^[0-9a-zA-z-]+:")
532 ;; If this function doesn't move anuy more, then the headers
534 (if (null (ti::mail-next-field-start 'move))
536 (setq point (point))))))
541 ;;; ----------------------------------------------------------------------
543 (defun ti::mail-text-start (&optional move)
544 "Return starting point or text in BODY. Optionally MOVE to it.
545 If buffer is not mail-like, then return `point-min'.
548 `mail-header-separator'"
549 (let ((re (regexp-quote mail-header-separator))
551 (when (ti::mail-mail-p)
553 ((save-excursion ;Do we find the separator?
555 (when (re-search-forward re nil t)
557 (setq point (point)))))
558 ((setq point (ti::mail-hmax))
562 (setq point (point))) )
564 (error "Can't find position.")))
565 (if (eq point (point-min)) ;Not found
566 (error "mail-header-separator not found or headers not found.")))
571 ;;; ----------------------------------------------------------------------
573 (defun ti::mail-point-at-header-p ()
574 "Return non-nil if point is at HEADER area of mail."
575 (< (point) (ti::mail-text-start)))
577 ;;; ----------------------------------------------------------------------
579 (defun ti::mail-point-at-body-p ()
580 "Return non-nil if point is at BODY of mail."
581 (not (ti::mail-point-at-header-p)))
583 ;;; ----------------------------------------------------------------------
584 ;;; - Many std emacs dist. functions work so that you have to narrow
585 ;;; to headers before you can call the functions.
587 (defun ti::mail-narrow (&optional text)
588 "Narrows to the headers only. Optionally to TEXT portion."
590 (narrow-to-region (ti::mail-text-start 'move) (point-max))
591 (narrow-to-region (point-min) (ti::mail-hmax))))
593 ;;; ----------------------------------------------------------------------
594 ;;; - This is for both GNUS and RMAIL
596 (defun ti::mail-mail-buffer-name ()
597 "Find original mail buffer whether in GNUS or in RMAIL.
604 ;; It's amazing that GNUS uses pointers and RMAIL uses string ...
605 (let ((buffer (if (boundp 'mail-reply-buffer)
606 (symbol-value 'mail-reply-buffer))))
607 (cond ;what is the mail YANK buffer name?
610 ((and (not (null buffer))
612 (buffer-name buffer))
616 ;;; ----------------------------------------------------------------------
618 (defun ti::mail-generate-buffer-name (&rest ignore)
619 "Rename the *mail* buffer to \"*mail* SENDER\". IGNORE args.
620 You can install this function e.g. into
621 `my-message-generate-new-buffers' or `mail-setup-hook'"
622 (interactive "Pbuffer name: ")
623 (let ((to (if (string= (buffer-name) " *gnus article copy*") ;; See gnus-msg.el
624 (mail-fetch-field "From")
625 (mail-fetch-field "To")))
627 (unless (ti::nil-p to)
629 ((setq str (ti::string-match "\\([^@<]+\\)," 1 to))
630 (setq str (concat str ", ...")))
631 ((setq str (ti::string-match "\\([^@<]+\\)" 1 to)))
635 (setq str (replace-regexp-in-string "['\"]" "" str)) ;remove extra cruft
639 (if (ti::mail-news-buffer-p)
645 ;;; ----------------------------------------------------------------------
646 ;;; - The idea is to find three fields and see what they contain,
647 ;;; and do they exist?
648 ;;; - What's the use of this function? Well, when you post an article
649 ;;; or mail it, you can call this function from some of those
650 ;;; posting hooks to determine what to do with the buffer.
652 ;;; - code lines disabled now so that it buffer can be checked any time
654 (defun ti::mail-mail-simple-p ()
655 "Check if buffer contain headers belonging to simple \\[mail].
656 You can call this only once, just after the buffer is initially created"
657 (require 'mail-utils)
658 (let* ((sub (mail-fetch-field "Subject"))
659 ;; mail-fetch-field doesn't return nil if field is empty.
660 (to (mail-fetch-field "to"))
661 (news (mail-fetch-field "Newsgroups")))
662 ;; When you're replying to message in NEWS, RMAIL, the SUBJ and
663 ;; TO fields are already filled.
665 ;; That's why you can only call this function once.
666 ;; When you use C-x m, and fill the fields, there is no way
667 ;; to detect afterwards if the mail buffer was simple mail or not
670 ((or (string-match "news" (symbol-name 'major-mode))
673 ((and (ti::nil-p sub)
677 ;;; ----------------------------------------------------------------------
679 (defun ti::mail-to-list-p ()
680 "Check if message is meant to be sent to a mailing list.
681 In GNUS you need to add Group parameter `to-list' containing address
682 to mailing list or otherwise Group is not considered mailing list."
683 (when (featurep 'gnus)
684 (let* ((group (ti::mail-news-group)))
685 (when (stringp group)
686 (gnus-group-get-parameter group 'to-list) ))))
689 ;;{{{ macros: VM, RMAIL, GNUS
691 ;;; ----------------------------------------------------------------------
693 (put 'ti::mail-vm-macro 'lisp-indent-function 0)
694 (put 'ti::mail-vm-macro 'edebug-form-spec '(body))
695 (defmacro ti::mail-vm-macro (&rest body)
696 "Do BODY in VM's active buffer.
697 The `save-excursion' -- set buffer form is executed."
699 (let* ((BuffeR-S (when (boundp 'vm-mail-buffer)
700 (symbol-value 'vm-mail-buffer))))
701 (if (or (null BuffeR-S)
702 (not (buffer-live-p (get-buffer BuffeR-S))))
703 (error "vm-mail-buffer invalid")
704 (with-current-buffer BuffeR-S
707 ;;; ----------------------------------------------------------------------
709 (put 'ti::mail-mh-macro 'lisp-indent-function 0)
710 (put 'ti::mail-mh-macro 'edebug-form-spec '(body))
711 (defmacro ti::mail-mh-macro (&rest body)
712 "Do BODY in MH's active buffer.
713 The `save-excursion' -- set buffer form is executed."
715 (let* ((BuffeR-S (when (boundp 'mh-show-buffer)
716 (symbol-value 'mh-show-buffer))))
717 (if (or (null BuffeR-S)
718 (not (buffer-live-p (get-buffer BuffeR-S))))
719 (error "mh-show-buffer invalid")
720 (with-current-buffer BuffeR-S
723 ;;; ----------------------------------------------------------------------
725 (put 'ti::mail-gnus-macro 'lisp-indent-function 0)
726 (put 'ti::mail-gnus-macro 'edebug-form-spec '(body))
727 (defmacro ti::mail-gnus-macro (&rest body)
728 "Do BODY in Gnus `gnus-article-buffer' if it exists.
729 The `save-excursion' -- set buffer form is executed."
731 (let* ((BuffeR-S (when (boundp 'gnus-article-buffer)
732 (symbol-value 'gnus-article-buffer))))
733 (if (or (null BuffeR-S)
734 (not (buffer-live-p (get-buffer BuffeR-S))))
735 (error "gnus-article-buffer invalid")
736 (with-current-buffer BuffeR-S
739 ;;; ----------------------------------------------------------------------
741 (put 'ti::mail-rmail-macro 'lisp-indent-function 0)
742 (put 'ti::mail-rmail-macro 'edebug-form-spec '(body))
743 (defmacro ti::mail-rmail-macro (&rest body)
744 "Do BODY in RMAIL's active buffer. You have be in RMAIL summary."
747 ;; This variable is available in Rmail-summary
749 (or (if (boundp 'rmail-buffer) (symbol-value 'rmail-buffer))
750 (get-buffer "RMAIL"))))
751 (if (or (null BuffeR-R)
752 (not (buffer-live-p (get-buffer BuffeR-R))))
753 (error "rmail-buffer buffer invalid")
754 (with-current-buffer BuffeR-R
757 ;;; ----------------------------------------------------------------------
759 (put 'ti::mail-rmail-do-message-macro 'lisp-indent-function 2)
760 (put 'ti::mail-rmail-do-message-macro 'edebug-form-spec '(body))
761 (defmacro ti::mail-rmail-do-message-macro (nbr mode &rest body)
762 "Go to message without showing it and execute body.
763 Must be in RMAIL buffer already.
766 NBR message number, like `rmail-current-message'
767 MODE if non-nil then the area narrows to full stored message
768 with original headers. If nil, then area narrows to displayed
770 BODY forms to execute in are narrowed to message."
772 (let ((beg (rmail-msgbeg (, nbr)))
773 (end (rmail-msgend (, nbr))))
774 (save-window-excursion
779 (search-forward "\n*** EOOH ***\n" end t))
780 (narrow-to-region (point) end)
781 (goto-char (point-min))
784 ;;; ----------------------------------------------------------------------
786 (defun ti::mail-rmail-copy-message (&optional nbr separate)
787 "Copy message NBR with header. Defaults to `rmail-current-message'.
788 Current buffer must me in RMAIL already.
793 SEPARATE if non-nil, then the headers and message body are returned
794 separately in format (hdr-string . body-string)
805 (setq nbr (or nbr rmail-current-message)
806 beg (rmail-msgbeg nbr)
807 end (rmail-msgend nbr))
810 (error "NBR %s" nbr))
811 ;; The BEG isn't exactly the message beginning, skip 3 lines,
812 ;; also don't copy the original heades only.
816 ;; Summary-line: 23-Mar #Please Help Yourself, Help Ot...
817 ;; <ORIGINAL HEADERS>
820 ;; <HEADERS SHOWN IN RMAIL>
824 (goto-char beg) (forward-line 3)
826 (re-search-forward "^[ \t]*$")
827 (setq hdr (buffer-substring beg (point)))
828 ;; Already sitting at empty line, move away.
830 (re-search-forward "^[ \t]*$")
832 ;; Now make HDR + BODY of message
834 (setq ret (cons hdr (buffer-substring beg end)))
835 (setq ret (concat hdr (buffer-substring beg end)))))
840 ;;{{{ PGP general, tests
842 ;;; ----------------------------------------------------------------------
844 (defun ti::mail-pgp-v3xx-p ()
845 "Check if X-Pgp v3.xx header signing is in use.
846 It should have VALUE = KEYWORD; statement."
849 (when (re-search-forward "X-Pgp-signed" nil t)
853 (looking-at "^[ \t]+.*=.*;"))))
855 ;;; ----------------------------------------------------------------------
857 (defun ti::mail-pgp-p ()
858 "Check if buffer contain PGP. It must have left flushed regexp:
859 \"^-----BEGIN.*PGP +\\(SIGNATURE\\|SIGNED\\\\|MESSAGE)\", otherwise this
860 string may be inside quoted text.
862 If there is X-pgp-sig.*: header, then it's also considered PGP message."
863 (let ((max (ti::mail-hmax))) ;headers ?
864 ;; if headers was found use that.
866 (setq max (if (eq (point-min) max)
871 (or (let (case-fold-search)
873 "^-----BEGIN PGP \\(SIGNATURE\\|SIGNED\\|MESSAGE\\)"
876 ;; The New PGP in headers standard.
877 (re-search-forward "^X-Pgp-Sig.*:" max t))))))
879 ;;; ----------------------------------------------------------------------
881 (defun ti::mail-pgp-signed-conventional-p ()
882 "Return t if message is conventionally signed."
883 (save-excursion (ti::pmin) (ti::mail-pgp-re-search 'sig)))
885 ;;; ----------------------------------------------------------------------
887 (defun ti::mail-pgp-signature-detached-p ()
888 "Return (beg . end) if there is detached signature."
889 (let* ((point (point))
892 (prog1 (save-excursion
894 (unless (ti::mail-pgp-re-search 'msg) ;Must not exist
895 (and (setq beg (ti::mail-pgp-re-search 'sig))
896 (setq end (ti::mail-pgp-re-search 'sige))
900 ;;; ----------------------------------------------------------------------
902 (defun ti::mail-pgp-signed-conventional-multi-p ()
903 "Return t if message is signed conventionally multiple times."
906 (ti::mail-pgp-re-search 'sig 'move)
908 (ti::mail-pgp-re-search 'sig 'move)))
910 ;;; ----------------------------------------------------------------------
912 (defun ti::mail-pgp-signed-xpgp-p ()
913 "Return t if message is X-pgp signed.
914 There may be X-Pgp headers, but if the message is already
915 verified, that removes the signature around encrypted
916 message \"- -----BEGIN PGP MESSAGE-----\"
917 --> \"-----BEGIN PGP MESSAGE-----\"
918 In this case the message is no more in signed format,
919 but in encrypted format."
920 (and (ti::mail-pgp-headers-p)
921 ;; See documentation above
924 (null (re-search-forward
925 (concat "^" (ti::mail-pgp-msg-begin-line))
928 ;;; ----------------------------------------------------------------------
930 (defun ti::mail-pgp-signed-p ()
931 "Return t is message is conventionally or X-pgp signed."
932 (or (ti::mail-pgp-signed-xpgp-p)
933 (ti::mail-pgp-signed-conventional-p)))
935 ;;; ----------------------------------------------------------------------
937 (defun ti::mail-pgp-public-key-p (&optional point)
938 "Find public key delimiter from current point forward or using POINT."
940 (goto-char (or point (point)))
941 (re-search-forward (ti::mail-pgp-pkey-begin-line) nil t)))
943 ;;; ----------------------------------------------------------------------
945 (defun ti::mail-pgp-remail-p ()
946 "Check if This is remailer message."
949 (re-search-forward "[:#][:#]+\nReply-To" nil t)))
951 ;;; ----------------------------------------------------------------------
953 (defun ti::mail-pgp-comment-file-p (&optional point)
954 "You can send binary files with base64 signing.
955 This function checks if comment block has have words 'File: FILE '.
959 -----BEGIN PGP MESSAGE-----
961 Comment: Base64 signed. File: tm.tar uncompresses to approx. 20K
965 POINT search start point
972 (if point (goto-char point))
973 (when (re-search-forward "^Comment:.*File:? +\\([^ \t,]+\\)" nil t)
976 ;;; ----------------------------------------------------------------------
978 (defun ti::mail-pgp-encrypted-p (&optional check-pgp-dash-line)
979 "Check if there is encrypted PGP message.
980 It must have left flushed tag. The start point of match is returned.
981 The following tag will tell if if the message is encrypted.
988 CHECK-PGP-DASH-LINE if the tag is not found, message _could_ be signed
989 if there is -----BEGIN PGP MESSAGE----- tag.
990 When this flag is non-nil, it also checks this
991 case. Beware: message could be base64 signed too,
992 so the encrypted-p test may not be exactly right."
995 (if (re-search-forward "::[ \t]*\nEncrypted:[ \t]*PGP" nil t)
997 (when check-pgp-dash-line
999 (car-safe (ti::mail-pgp-block-area 'msg))))))
1001 ;;; ----------------------------------------------------------------------
1003 (defun ti::mail-pgp-normal-p (&optional point)
1004 "Check if there is any PGP in current buffer from POINT forward.
1005 The beginning point of PGP is returned."
1006 ;; Must find at least two lines, maybe BEG and END
1007 (let ((re (ti::mail-pgp-any-pgp-line-regexp 'acnhor))
1011 (when (re-search-forward re nil t)
1012 (setq ret (match-beginning 0))
1013 (if (null (re-search-forward re nil t))
1017 ;;; ----------------------------------------------------------------------
1019 (defun ti::mail-pgp-headers-p ()
1020 "Return t if PGP information is in headers.
1021 Searches string 'X-Pgp-Signed:' and return end of match or nil."
1022 (let ((psig "^X-Pgp-Signed:")
1023 (hmax (ti::mail-hmax)))
1026 (re-search-forward psig hmax t))))
1028 ;;; ----------------------------------------------------------------------
1030 (defun ti::mail-pgp-re (str)
1031 "Add possible beginning anchor if STR doesn't have one."
1032 (if (not (char= (aref str 0) ?^))
1035 ;;; ----------------------------------------------------------------------
1037 (defun ti::mail-pgp-block-area-kill-forward (mode &optional move)
1038 "Search PGP block forward and kill it. If no block found, do nothing.
1041 MODE choices are explained in `ti::mail-pgp-block-area'.
1042 MOVE if non-nil, move to killed region begin point."
1044 (when (setq reg (ti::mail-pgp-block-area mode))
1045 (delete-region (car reg) (cdr reg))
1046 (when move (goto-char (car reg))))))
1048 ;;; ----------------------------------------------------------------------
1050 (defun ti::mail-pgp-block-area (mode &optional inside max nstrict)
1051 "Return (beg . end) of PGP block from current point forward.
1054 MODE nil search signed start line..
1055 'sig search signature start instead.
1056 'signed search signed message area
1057 'pkey search public key block start instead.
1058 'msg search for pgp base64 signed \"message\"
1059 'any go to `point-min' and search beginning of any
1060 PGP line, then go to the end of buffer and search
1061 backward any PGP line. The lines must not be at
1062 same position. This gives you the whole PGP
1065 INSIDE if non-nil, beg and end are 'inside' without the PGP tags.
1066 MAX max point to search
1067 NSTRICT If non-nil; then the pgp bounds must not be left flushed,
1068 but can contains \"- -\".
1074 ((null mode) (ti::mail-pgp-signed-begin-line))
1075 ((eq 'sig mode) (ti::mail-pgp-signature-begin-line))
1076 ((eq 'pkey mode) (ti::mail-pgp-pkey-begin-line))
1077 ((eq 'msg mode) (ti::mail-pgp-msg-begin-line))
1078 ((eq 'any mode) (ti::mail-pgp-any-pgp-line-regexp (not nstrict)))
1079 ((eq 'signed mode) (ti::mail-pgp-signed-begin-line))
1081 (error "unknown mode"))))
1084 ((null mode) (ti::mail-pgp-signed-end-line))
1085 ((eq 'sig mode) (ti::mail-pgp-signature-end-line))
1086 ((eq 'pkey mode) (ti::mail-pgp-pkey-end-line))
1087 ((eq 'msg mode) (ti::mail-pgp-msg-end-line))
1088 ((eq 'signed mode) (ti::mail-pgp-signed-end-line))))
1096 (when (re-search-forward re1 max t)
1097 (setq beg (match-beginning 0))
1098 (when (re-search-forward re1 max t)
1100 (when (not (eq beg (point)))
1102 (setq ret (cons beg (point)))))))
1105 (setq re1 (concat "^-? ?" re1)
1106 re2 (concat "^-? ?" re2))
1107 (setq re1 (concat "^" re1)
1108 re2 (concat "^" re2)))
1109 (when (re-search-forward re1 max t)
1112 (beginning-of-line))
1115 (when (re-search-forward re2 max t)
1121 (setq ret (cons beg end)))))))
1124 ;;; ----------------------------------------------------------------------
1126 (defun ti::mail-pgp-re-search (&optional mode move end no-anchor)
1127 "Re-search-forward to find -----BEGIN.*SIGNED.
1130 MODE nil search signed start line.
1131 'sig search signature start.
1132 'sige search signature block end.
1133 'pkey search public key block start.
1134 'pkeye search public key block end.
1135 'msg search for pgp base64 signed \"message\"
1136 This also finds conventionally crypted tag.
1137 'kid search for 'Key for user ID: '
1138 'kpub search for 'pub 512/47141D35 1996/06/03 ...'
1139 note: match level 1 matches 0x code 47141D35
1140 MOVE flag non-nil moves point to found point
1141 END flag use `match-end' instead of math-beginning.
1142 NO-ANCHOR flag non-nil disables using '^' anchor.
1145 point ,beginning of line
1148 ((null mode) (ti::mail-pgp-signed-begin-line))
1149 ((eq 'sig mode) (ti::mail-pgp-signature-begin-line))
1150 ((eq 'sige mode) (ti::mail-pgp-signature-end-line))
1151 ((eq 'pkey mode) (ti::mail-pgp-pkey-begin-line))
1152 ((eq 'pkeye mode) (ti::mail-pgp-pkey-end-line))
1153 ((eq 'msg mode) (ti::mail-pgp-msg-begin-line))
1154 ((eq 'kid mode) "Key for user ID: ")
1156 "pub[ \t]+[0-9]+/\\([A-Z0-9]\\)+[ \t]+.*/.*/[0-9]")
1158 (error "unknown mode"))))
1160 (when (and (null no-anchor)
1161 (not (memq mode '(kid))))
1162 ;; suppose encrypted and signed message
1163 ;; - -----END PGP MESSAGE-----
1165 (setq re (concat "^-? ?" re)))
1167 (if (or (looking-at re)
1168 (re-search-forward re nil t))
1170 (setq point (match-end 0))
1171 (setq point (match-beginning 0)))))
1172 (if (and move point)
1179 ;;; ----------------------------------------------------------------------
1181 (defun ti::mail-pgp-exe-version-string (&optional exe-file-location)
1182 "Call pgp/gpg executable to find out its version number.
1183 EXE-FILE-LOCATION defaults to \"pgp\" but can also be absolute path."
1185 (call-process (or exe-file-location "pgp")
1189 ;; - With PGP will say "illegal option", but will print
1191 ;; - With GPG will print logo screen.
1194 (when (or (re-search-forward
1195 "Pretty Good Privacy(tm) +\\([^\r\n ]+\\)" nil t)
1197 "gpg (GnuPG) +\\([^\r\n ]+\\)" nil t))
1200 ;;; ----------------------------------------------------------------------
1202 (defun ti::mail-pgp-data-type ()
1203 "Examine pgp data packet type by searching _forward_.
1205 'base64 'pgp 'conventional or nil"
1206 (let ((re (ti::mail-pgp-any-pgp-line-regexp 'anchor))
1209 (when (and (re-search-forward re nil t)
1210 (re-search-forward "^$" nil t))
1211 ;; #todo: Check first character. Actually we should check bit mask...
1213 ;; -----BEGIN PGP MESSAGE-----
1215 ;; Comment: Encrypted by xxx
1219 (setq char (following-char))
1221 ((char= char ?p) 'conventional)
1222 ((char= char ?h) 'pgp)
1223 ((char= char ?o) 'base64))))))
1225 ;;; ----------------------------------------------------------------------
1227 (defun ti::mail-pgp-trim-buffer ()
1228 "Trim buffer: pgp blocks are left flushed and junk around them is removed."
1234 (setq region (ti::mail-pgp-block-area 'any)))
1236 (when (setq stat (ti::mail-pgp-chop-region (car region) (cdr region)))
1237 (goto-char (cdr stat)))))))
1239 ;;; ----------------------------------------------------------------------
1240 ;;; - This is needed after finger or http call to clean up all unnecessary
1241 ;;; tags around the PGP key.
1243 (defun ti::mail-pgp-chop-region (beg end)
1244 "Delete junk around BEG END from pgp public key block.
1245 Area BEG END that correspond to pgp begin and end
1246 lines (call `ti::mail-pgp-block-area' with argument 'any),
1247 then we chop the public key region so that only the pgp area
1248 is left without additional garbage.
1251 (beg .end) the canonilized area of PGP block
1256 <SAMP> -----BEGIN PGP PUBLIC KEY BLOCK-----</SAMP>
1257 <SAMP> Version: 2.6.3ia</SAMP>
1260 <SAMP> mQBNAzGzQ2MAAAECAM4p2THKCpNjYXDLpsg4sLHyEiNxJwQuEYfipdTj</SAMP>
1261 <SAMP> p5CPHN+0LkphcmkgQWFsdG8sIEZpbmxhbmQgPGphcmkuYWFsdG9AbnRj</SAMP>
1262 <SAMP> LmNvbT6JAFUDBRAxs0O+wLrt1UcUHTUBAbMhAf9Qgh6EznEcY2OUOIPg</SAMP>
1264 <SAMP> -----END PGP PUBLIC KEY BLOCK-----</SAMP>
1266 This is converted into
1268 -----BEGIN PGP PUBLIC KEY BLOCK-----
1269 Version: 2.6.3ia</SAMP>
1271 mQBNAzGzQ2MAAAECAM4p2THKCpNjYXDLpsg4sLHyEiNxJwQuEYfipdTj
1272 p5CPHN+0LkphcmkgQWFsdG8sIEZpbmxhbmQgPGphcmkuYWFsdG9AbnRj
1273 LmNvbT6JAFUDBRAxs0O+wLrt1UcUHTUBAbMhAf9Qgh6EznEcY2OUOIPg
1275 -----END PGP PUBLIC KEY BLOCK-----"
1277 (goto-char beg) (beginning-of-line)
1278 (ti::narrow-safe (point) (progn
1282 (ti::buffer-fill-region-spaces (point-min) (point-max))
1284 (re-search-forward "-----END")
1285 (goto-char (match-beginning 0))
1286 (if (> (current-column) 0) ;Nothing to do, it's left flushed
1287 (delete-rectangle (point-min) (point)))
1289 (ti::buffer-replace-regexp "<.*$" 0 "")
1290 ;; Because the last line does not have newline, the
1291 ;; previous regexp doesn't match. Fix the last line too.
1292 (goto-char (point-max))
1294 (let (case-fold-search) ;be sensitive
1295 ;; -----END PGP PUBLIC KEY BLOCK-----
1296 (if (and (looking-at ".*[A-Z]-----\\(.*\\)")
1298 (ti::replace-match 1)))
1299 (setq beg (point-min)
1304 ;;{{{ PGP signed headers
1306 ;;; ...................................................... &pgp-header ...
1308 ;;; ----------------------------------------------------------------------
1310 (defun ti::mail-pgp-header-kill-in-body ()
1311 "Kill headers that are inserted into the body of message.
1312 If there is no headers, this function does nothing.
1314 --text follows this line-- [or empty line after headers]
1322 (ti::mail-text-start 'move)
1324 (when (and (looking-at "^##\n")
1325 (re-search-forward "^$" nil t))
1326 (delete-region beg (point))))))
1329 ;;{{{ PGP ASCII armor
1331 ;;; ....................................................... &pgp-armor ...
1333 ;;; ----------------------------------------------------------------------
1335 (defun ti::mail-pgp-data-char-to-int (char)
1336 "Process PGP ascii armor data.
1337 Input is ASCII armor CHAR (as one string). Function return respective int."
1338 (let* ((table (concat "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
1339 "abcdefghijklmnopqrstuvwxyz0123456789+/"))
1342 (if (null (setq str (ti::string-match
1343 (concat ".*" (regexp-quote char)) 0 table)))
1344 (error "Armor char is invalid %s " char)
1345 (1- (length str)))))
1347 ;;; ----------------------------------------------------------------------
1349 (defun ti::mail-pgp-data-string-to-bin-string (string)
1350 "Process PGP ascii armor data.
1351 Convert quoted printable ASCII armor STRING into binary string."
1352 (let* ((len (length string))
1359 (setq ch (substring string i (1+ i)))
1360 (setq int (inline (ti::mail-pgp-data-char-to-int ch)))
1361 (setq bin (inline (int-to-bin-string int 6)))
1362 (setq ret (concat ret bin))
1366 ;;; ----------------------------------------------------------------------
1368 (defun ti::mail-pgp-data-bin-string-to-int-list(string)
1369 "Process PGP ascii armor data.
1370 Convert 8bit binary byte string \"000001...\" into list of ints."
1371 (let* ((len (/ (length string) 8))
1377 (setq bin (substring string (* i 8) (+ 8 (* i 8))))
1378 (setq int (inline (bin-string-to-int bin)))
1383 ;;; ----------------------------------------------------------------------
1385 (defsubst ti::mail-pgp-data-ascii-armor-convert (string)
1386 "Convert PGP ascii armor STRING(quoted printable) into list of ints."
1387 (ti::mail-pgp-data-bin-string-to-int-list
1388 (ti::mail-pgp-data-string-to-bin-string string)))
1390 ;;; ----------------------------------------------------------------------
1392 (defun ti::mail-pgp-data-study-ctb-byte (int)
1393 "From single INT, examine the PGP CTB structure.
1395 nil ,input was not CTB byte
1396 '(ctb-type length-field)
1398 'enc (pgp encrypted message)
1399 'signed (signed message)
1400 'secring (secret keyring)
1401 'pring (public keyring)
1402 'base64 (base 64 signed)
1403 'crypt (conventionally crypted)
1404 'raw (raw literal plaintext)
1405 'trust (keyring trust packet)
1406 'uid (user id packet)
1407 'comment (comment packet)
1408 'unknown (none of the above...)
1411 nil no length, unknown length
1412 1 2 4 byte packet length"
1413 (let* ((length-mask 3) ;; 00000011b
1414 (type-mask 60) ;; 00111100b
1415 (ctb-mask 128) ;; 10000000b
1430 (when (logand int ctb-mask)
1432 ;; shift to the right 2 bits
1434 (when (setq val (assq (lsh (logand int type-mask) -2) table))
1435 (setq type (cdr val)))
1437 (setq val (logand int length-mask))
1439 ((eq 0 val) (setq val 1))
1440 ((eq 1 val) (setq val 2))
1441 ((eq 1 val) (setq val 4))
1442 ((eq 3 val) (setq val nil)))
1443 (setq ret (cons type val)))
1446 ;;; ----------------------------------------------------------------------
1448 (defsubst ti::mail-pgp-stream-study-1-ver (int)
1449 "Return pgp version string from stream INT."
1453 (t (error "Invalid Data format."))))
1455 ;;; ----------------------------------------------------------------------
1457 (put 'ti::mail-pgp-stream-study-1-key-id 'lisp-indent-function 1)
1458 (put 'ti::mail-pgp-stream-study-1-key-id 'edebug-form-spec '(body))
1459 (defmacro ti::mail-pgp-stream-study-1-key-id (stream result)
1460 "Read MSB and LSB key-id from STREAM to RESULT.
1461 STREAM will be advanced during read."
1465 (setq (, result) (concat (or (, result) "")
1466 (format "%02x" (car (, stream))))
1467 (, stream) (cdr (, stream))
1469 (setq (, result) (upcase (, result))))))
1471 ;;; ----------------------------------------------------------------------
1473 (defun ti::mail-pgp-stream-study-1-time (stream)
1474 "Read TIME from STREAM to RESULT."
1477 ;; There must be easier way to do, but right now it goes like this
1479 ;; --> hex 339E 5F91
1480 ;; --> int 13214 24464 which is in (current-time) format
1483 (setq val1 (hexl-hex-string-to-integer
1485 (int-to-hex-string (car stream))
1486 (int-to-hex-string (car (cdr stream)))))
1488 stream (cdr (cdr stream))
1489 val2 (hexl-hex-string-to-integer
1491 (int-to-hex-string (car stream))
1492 (int-to-hex-string (car (cdr stream))))))
1493 (ti::date-standard-date nil (list val1 val2))))
1495 ;;; ----------------------------------------------------------------------
1497 (defun ti::mail-pgp-stream-study-enc (length stream)
1498 "Study the 'enc packet, which has known LENGTH.
1499 STREAM is list of ints, minimum 11 integers, 13 is the full 'enc packet.
1506 rsa-algorithm ;; nil if stream is not long enough
1507 rsa-int (encrypted integer)) ;; nil if stream is not long enough."
1514 ;; Skip to begin of real data
1515 ;; CTB LENGTH VERSION KEY-MSB KEY-LSB
1516 ;; 1byte 1-4bytes 1byte 4bytes 4bytes
1517 (setq stream (nthcdr (1+ length) stream))
1518 (setq val (car stream) stream (cdr stream))
1519 (setq ver (ti::mail-pgp-stream-study-1-ver val))
1520 (ti::mail-pgp-stream-study-1-key-id stream msb)
1521 (ti::mail-pgp-stream-study-1-key-id stream lsb)
1522 (setq rsa-alg (car stream)
1523 rsa-int (cadr stream))
1524 (list ver msb lsb rsa-alg rsa-int)))
1526 ;;; ----------------------------------------------------------------------
1528 (defun ti::mail-pgp-stream-study-signed (length stream)
1529 "Study the 'sign packet, which has known LENGTH. STREAM is list of ints.
1541 alg-rsa ;; nil if stream is not long enough
1542 alg-md5 ;; nil if ...
1543 digest '(int int);; nil if ...
1545 rsa-algorithm ;; nil if stream is not long enough
1546 rsa-int (encrypted integer) ;; nil if stream is not long enough)"
1556 ;; Skip to begin of real data
1557 ;; CTB LENGTH VERSION KEY-MSB KEY-LSB
1558 ;; 1byte 1-4bytes 1byte 4bytes 4bytes
1560 (setq stream (nthcdr (1+ length) stream))
1561 (setq ver (ti::mail-pgp-stream-study-1-ver (car stream))
1563 md-length (car stream)
1565 sig-class (car stream)
1566 stream (cdr stream))
1568 (ti::mail-pgp-stream-study-1-time stream))
1569 (setq stream (nthcdr 4 stream))
1570 (ti::mail-pgp-stream-study-1-key-id stream msb)
1571 (ti::mail-pgp-stream-study-1-key-id stream lsb)
1572 (setq alg-rsa (car stream)
1574 alg-md5 (car stream)
1576 digest (list (car stream) (car (cdr stream))))
1577 (list ver md-length sig-class timestamp msb lsb
1578 alg-rsa alg-md5 digest)))
1580 ;;; ----------------------------------------------------------------------
1582 (defun ti::mail-pgp-stream-study-pring (length stream)
1583 "Study the 'pring packet, which has known LENGTH. STREAM is list of ints.
1590 key-lsb-hex-string)"
1596 ;; Skip to begin of real data
1597 ;; CTB LENGTH VERSION TIME VALIDITY RSA-ALG
1598 ;; 1byte 1-4bytes 1byte 4bytes 2bytes 1byte
1599 (setq stream (nthcdr (1+ length) stream))
1600 (setq ver (ti::mail-pgp-stream-study-1-ver (car stream))
1601 stream (cdr stream))
1603 (ti::mail-pgp-stream-study-1-time stream))
1604 (setq stream (nthcdr 4 stream)
1605 validity (car stream)
1606 stream (cdr stream))
1607 ;; PGP format spec is not clear enough here!
1608 ;; Don't know where the User ID is...
1609 ;;; (ti::mail-pgp-stream-study-1-key-id stream msb)
1610 ;;; (ti::mail-pgp-stream-study-1-key-id stream lsb)
1611 (list ver timestamp validity msb lsb)))
1613 ;;; ----------------------------------------------------------------------
1615 (defun ti::mail-pgp-stream-study (ctb stream)
1620 CTB in format `ti::mail-pgp-data-study-ctb-byte'
1621 STREAM dearmored int stream (list of ints including ctb-byte)
1625 LIST depends on the ctb, see conversion functions."
1626 (let* ((type (car ctb))
1632 (ti::mail-pgp-stream-study-enc len stream))
1634 (ti::mail-pgp-stream-study-signed len stream))
1642 (ti::mail-pgp-stream-study-pring len stream)))))
1644 ;;; ----------------------------------------------------------------------
1646 (defun ti::mail-pgp-stream-forward-xpgp ()
1647 "If there is X-Pgp-Signed field, goto signature stream."
1648 (let* ((point (ti::mail-pgp-headers-p)))
1651 ;; must exist, this call dies if not found
1652 (re-search-forward "Signature[ \t]*=[ \t\n\"]+"))))
1654 ;;; ----------------------------------------------------------------------
1656 (defun ti::mail-pgp-stream-forward (&optional any)
1657 "Find PGP data stream block start forward. PGP block must be left flushed.
1661 ANY if non-nil, then find any stream (not necessarily left flushed)
1665 point cursor is placed in front of stream
1666 nil If there is no PGP stream block, do nothing."
1667 (let* ((beg (ti::mail-pgp-msg-begin-line))
1668 (sig (ti::mail-pgp-signature-begin-line))
1669 (pkey (ti::mail-pgp-pkey-begin-line))
1670 (re (concat (if any "" "^") "-----BEGIN"))
1676 ;; -----BEGIN PGP MESSAGE-----
1679 ;; owEBbACT/4kAVQMFATOb
1682 ;; -----BEGIN PGP SIGNATURE-----
1686 ;; iQBVAwUBM55fkcC67dVHFB01AQGnHwIAqe2OfkdcnQviGzCmy3KddnsE8uFkAeaV
1688 ;; conventional crypt
1689 ;; -----BEGIN PGP MESSAGE-----
1692 ;; pgAAACN9WXlrJFURU5Xgi+YyN
1695 ;; -----BEGIN PGP MESSAGE-----
1698 ;; hEwDwLrt1UcUHTUBAf9
1701 ;; Extracted public key
1702 ;; -----BEGIN PGP PUBLIC KEY BLOCK-----
1705 ;; mQBNAzOW770AAAECANDkXBfEbJk0gW41o52nLiktpThcBY+BMQCY5zyGCyUIbrDp
1706 (while (and loop (re-search-forward re nil t))
1707 (goto-char (match-beginning 0))
1709 (when (or (looking-at beg)
1712 (setq col (current-column))
1713 (when (re-search-forward "^[ \t]*$" nil t)
1716 (move-to-column col)
1717 (setq ret (point))))
1719 (end-of-line))) ;wrong match, Continue search
1721 ;; none found, return to original position.
1725 ;;; ----------------------------------------------------------------------
1727 (defun ti::mail-pgp-stream-forward-and-study (&optional search any)
1728 "Find PGP data stream forward and study it.
1730 If normal search fails, then find X-Pgp-Signed field's first
1735 SEARCH if non-nil, then search PGP starting from `point-min' if
1736 forward lookup fails.
1737 ANY if non-nil, find also non-left flushed stream.
1741 '(CTB . (INFO-LIST)) the CTB type and data for CTB
1742 nil no stream found forward."
1744 (let* ((point (point))
1750 (when (or (ti::mail-pgp-stream-forward any)
1753 (ti::mail-pgp-stream-forward any))
1754 (ti::mail-pgp-stream-forward-xpgp))
1755 ;; Will match all base64 characters (approx.)
1756 (setq line (ti::buffer-match "[^ \t\n\"\']+" 0)
1757 list (ti::mail-pgp-data-ascii-armor-convert line)
1758 ctb (ti::mail-pgp-data-study-ctb-byte (car list))
1759 data (ti::mail-pgp-stream-study ctb list))
1760 (setq ret (cons (car ctb) data)))
1761 (unless ret (goto-char point)) ;Nothing found, return to point
1764 ;;; ----------------------------------------------------------------------
1766 (defun ti::mail-pgp-stream-forward-info (&optional search any)
1767 "Find PGP data stream and read some information. Return string.
1771 SEARCH if non-nil, then search PGP starting from `point-min' if
1772 forward lookup fails.
1773 ANY if non-nil, find also non-left flushed stream."
1779 (when (setq data (ti::mail-pgp-stream-forward-and-study search any))
1780 (setq type (car data))
1783 (setq ver (ti::mail-pgp-stream-data-elt data 'ver)
1784 time (ti::mail-pgp-stream-data-elt data 'time)
1785 key-id (ti::mail-pgp-stream-data-elt data 'key-id))
1786 (setq ret (format "Signed by 0x%s %s [v%s.x]" key-id time ver)))
1788 (setq ver (ti::mail-pgp-stream-data-elt data 'ver)
1789 key-id (ti::mail-pgp-stream-data-elt data 'key-id))
1790 (setq ret (format "Encrypted to 0x%s [v%s.x]" key-id ver)))
1792 (setq ver (ti::mail-pgp-stream-data-elt data 'ver)
1793 time (ti::mail-pgp-stream-data-elt data 'time))
1794 (setq ret (format "Public key %s [v%s.x]" time ver)))))
1797 ;;; ----------------------------------------------------------------------
1799 (defun ti::mail-pgp-stream-data-elt (data elt)
1800 "Study DATA and Return packet ELT.
1801 DATA must be in the format of `ti::mail-pgp-stream-forward-and-study'
1802 ELT can be 'ver 'time 'key-id"
1803 (let* ((type (car data))
1810 (signed ((ver 0) (time 3) (key-id 5)))
1811 (pring ((ver 0) (time 1) (key-id 4)))
1812 (enc ((ver 0) (key-id 2))) ;No TIME field
1814 (base64 ((ver 0) (time 3) (key-id 5)))
1815 (crypt ((ver 0) (time 3) (key-id 5)))))))))
1817 (error "Wrong specification %s %s %s" type elt data)
1818 (nth (nth 1 pos) (cdr data)))))
1820 ;;; Test suite with live data: first ASCII armor bytes
1822 ;; (setq list (ti::mail-pgp-data-ascii-armor-convert "hEwDwLrt1UcUHTUBA"))
1823 ;; (setq ctb (ti::mail-pgp-data-study-ctb-byte (car list)))
1824 ;; (setq data (ti::mail-pgp-stream-study ctb list))
1827 ;; (setq s "iQBVAwUBM55fkcC67dVHFB01AQGnHwIAqe2OfkdcnQviGzCmy3KddnsE8uFkAeaV")
1829 ;; (setq list (ti::mail-pgp-data-ascii-armor-convert s))
1830 ;; (setq ctb (ti::mail-pgp-data-study-ctb-byte (car list)))
1831 ;; (setq data (ti::mail-pgp-stream-study ctb list))
1836 ;;; ----------------------------------------------------------------------
1838 (defun ti::mail-pgpk-id-lines-in-region (beg end)
1839 "Search all lines in BEG END matching pgp -kvc and -kx lines.
1843 pub 1024/01234567 1997/05/01 Mar Bar <Bar@bar.com>
1847 Key for user ID: Mr. Bar <bar@bar.com>
1848 1024-bit key, key ID 01234567, created 1997/05/01
1850 And return list of those lines."
1852 (ti::buffer-grep-lines
1853 "pub[ \t:]+[0-9]+/[A-Z0-9]+[ \t:]+.*/.*/[0-9]" beg end))
1854 (l2 (ti::buffer-grep-lines "[0-9]-bit key, Key ID " beg end)))
1856 ((and l1 l2) (ti::list-merge-elements l1 l2))
1860 ;;; ----------------------------------------------------------------------
1862 (defun ti::mail-pgpk-id-0x-lines-in-region (beg end)
1863 "Call `ti::mail-pgpk-id-lines-in-region' on BEG END, return only Key HEX ids."
1865 (dolist (line (ti::mail-pgpk-id-lines-in-region beg end))
1866 (when (stringp line)
1869 (ti::string-match "pub[ \t]+[0-9]+/\\([^ \t]+\\)" 1 line)
1870 (ti::string-match "Key ID \\([0-9A-F]+\\)" 1 line))
1874 ;;; ----------------------------------------------------------------------
1876 (defun ti::mail-pgpk-public-get-region (&optional beg end buffer relax)
1877 "Get all keys in BEG END from BUFFER and build list of key data.
1878 The blocks searched are in following format.
1880 Key for user ID: Mr. Foo <foo@example.com>
1881 512-bit key, key ID 123456789, created 1996/06/03
1882 Also known as: Mr Foo <bar@bar.com>
1884 -----BEGIN PGP PUBLIC KEY BLOCK-----
1886 -----END PGP PUBLIC KEY BLOCK-----
1890 If there _no_ 'Key for user ID:' string in the buffer, this function
1891 can't find the public key block while it may be there. It is
1892 assumed that each p-key block is _preceded_ by that string.
1894 All anonymous p-key block are skipped.
1898 If there are two sequential key-id strings, like
1899 Key for user ID: <<A
1900 Key for user ID: <<B
1901 -----BEGIN PGP PUBLIC KEY BLOCK-----
1903 The p-key block in the list for A will be nil.
1907 If RELAX argument is non-nil, then the 'Key for user ID:'
1908 must not exit. Only the Public key tags are searched.
1910 Recommended way of informing public keys is however displaying
1911 full public key information and not just PK block
1915 '((KEY-ID-STRING PUBLIC-KEY_BLOCK)"
1916 (let ((opt (if relax 'pkey 'kid))
1922 (with-current-buffer (or buffer (current-buffer))
1923 (ti::narrow-safe (or beg (point-min)) (or end (point-max))
1925 (while (ti::mail-pgp-re-search opt 'move)
1926 (setq id (ti::read-current-line))
1932 ;; And there is no public key between these two, set the
1933 ;; search limit to stop to next Key-id line.
1937 (setq max (ti::mail-pgp-re-search 'kid))))
1938 ;;; (ti::d! ">>" id ">>" max (ti::mail-pgp-block-area 'pkey nil max))
1940 ((setq region (ti::mail-pgp-block-area 'pkey nil max))
1941 (setq block (buffer-substring (car region) (cdr region)))
1942 (goto-char (cdr region)))
1946 (push (list id block) ret)
1947 (setq id nil block nil))))
1951 ;;{{{ PGP signature info
1953 ;;; ................................................... &pgp-signature ...
1955 ;;; ----------------------------------------------------------------------
1957 (defun ti::mail-pgp-signature-remove (&optional add no-cnv)
1958 "Remove PGP signature (and headers that have included in there).
1959 Below, only lines 7 and 8 are left in buffer.
1961 1 -----BEGIN PGP SIGNED MESSAGE----
1963 3 ## << Header start mark
1969 9 -----BEGIN PGP SIGNATURE-----
1973 The tag lines are reassembled and point sits at the beginning of line 6
1974 and whitespaces around (email) buffer text are deleted.
1975 If tag lines are found while ADD, this function does nothing.
1979 When removing signature, do not convert '- -' back into '-'.
1980 Eg. If message is encrypted and signed; it is not desirable to
1981 do this conversion if you just want to strip out the signature to Xpgp.
1982 The '- -' lines must stay there."
1983 (let* ((beg (save-excursion (ti::pmin) (ti::mail-pgp-re-search))))
1985 ((and add (null beg))
1986 (ti::mail-trim-buffer)
1987 (ti::mail-text-start 'move)
1988 (insert (ti::mail-pgp-signed-begin-line) "\n\n")
1990 (insert "\n" (ti::mail-pgp-signature-begin-line) "\n"))
1992 ;; there is one thing to fix first, PGP converts lines that have
1993 ;; double '--' at front
1999 ;; Let's correct those lines too.
2001 (save-excursion (ti::buffer-replace-regexp "^- -" 0 "-")))
2002 (when beg ;Thre is regular PGP sig
2003 ;; note: We don't trim BODY here, we only remove
2004 ;; the pgp tag lines. The receiving end should do
2005 ;; the trimming. (we save one function call)
2006 (goto-char beg) ;One newline at beg
2007 (ti::buffer-kill-line 'del 2) ;TWO lines; important
2008 ;; Kill included headers
2009 (when (and (looking-at "##\n.*: ")
2010 (re-search-forward "^$" nil t))
2011 (delete-region beg (1+ (point))))
2013 (when (and (prog1 (setq beg (ti::mail-pgp-re-search 'sig 'move))
2014 (ti::buffer-kill-line))
2015 (ti::mail-pgp-re-search 'sige 'move))
2017 (delete-region beg (point))))))))
2019 ;;; ----------------------------------------------------------------------
2021 (put 'ti::mail-pgp-signature-normal-do-region 'lisp-indent-function 0)
2022 (put 'ti::mail-pgp-signature-normal-do-region 'edebug-form-spec '(body))
2023 (defmacro ti::mail-pgp-signature-normal-do-region (&rest body)
2024 "Execute BODY and calculate pgp signature region.
2025 In the macro you can use following variables:
2027 `limits' (area-beg . area-end)
2031 This macro does nothing if there is no normal PGP signature."
2036 (setq limits (ti::mail-pgp-block-area 'sig))
2039 (setq area-beg (car limits)
2040 area-end (cdr limits))
2041 ;; If these are no used in BODY: no-op Quiet XE ByteCompiler
2042 (if (null area-beg) (setq area-beg nil))
2043 (if (null area-end) (setq area-end nil))
2046 ;;; ----------------------------------------------------------------------
2048 (defsubst ti::mail-get-article-buffer ()
2049 "Do `set-buffer' to *Article* if it exists. Return nil if no buffer."
2050 (if (boundp 'gnus-article-buffer)
2051 (symbol-value 'gnus-article-buffer)))
2053 ;;; ----------------------------------------------------------------------
2055 (put 'ti::mail-with-article-buffer 'lisp-indent-function 0)
2056 (put 'ti::mail-with-article-buffer 'edebug-form-spec '(body))
2057 (defmacro ti::mail-with-article-buffer (&rest body)
2058 "Run BODY in *Article* buffer if it exists."
2060 (let* ((buffer (ti::mail-get-article-buffer)))
2061 (when (buffer-live-p buffer)
2062 (with-current-buffer buffer
2065 ;;; ----------------------------------------------------------------------
2067 (defun ti::mail-pgp-signature-normal-info ()
2068 "Return signature information from normal PGP format.
2070 ((beg . end) (fld fld ..) (signarure-data sig ..))"
2074 (ti::mail-pgp-signature-normal-do-region
2076 (goto-char area-beg)
2078 ;; Here are the comments and other PGP headers
2079 (while (looking-at "^[^ \t]+:+ .*")
2080 (ti::nconc info-list (ti::read-current-line))
2082 ;; Here is the signature itself
2083 (while (not (>= (point) (cdr limits)))
2084 (if (looking-at "^[^ \t\n]+$")
2085 (ti::nconc sig-list (match-string 0)))
2087 (setq ret (list limits info-list sig-list))))
2090 ;;; ----------------------------------------------------------------------
2092 (defun ti::mail-pgp-sig-header-info-v2xx ()
2093 "Return signature information from X-pgp v2.xx headers.
2097 X-Pgp-Comment: Processed by TinyPgp.el 1.56
2098 X-Pgp-Version: 2.6.3ia
2099 X-Pgp-Charset: noconv
2101 iQBVAwUBMoijBMC67dVHFB01AQGf3QH/dmgc47fx1tvHYPcuKWIz0Fe7HnWXmd63
2102 3IBA6vhSqzbUT4nkKL2QJQX/0Z8I9dkmOahSQNKvU/7qsB9Iw8JwpQ==
2106 ((beg . end) (fld fld ..) (signature-data sig ..))"
2107 (let* ((case-fold-search t)
2109 (p-re (concat "^" pbase)) ;pgp regexp for hdrs
2110 (psig (concat p-re "Signed:"))
2113 "\\(Version:\\|Charset:\\|Comment:\\|Signed:\\)"))
2114 (hmax (ti::mail-hmax))
2125 (< (point) hmax) ;those fwl-line calls may go past...
2126 (re-search-forward fld-re hmax t))
2128 (if (null beg) ;record it NOW
2131 ((looking-at (concat psig "[ \t]*\\([^ \t\n]*\\)"))
2132 ;; Is this the signature itself ? Special handling,
2133 ;; because spreads multiple lines.
2134 (setq val (ti::remove-properties (match-string 1)))
2135 (if (not (string= "" val))
2136 (ti::nconc sig-list val))
2138 (while (looking-at "^[ \t]+\\([^ \t\n]+\\)")
2139 (ti::nconc sig-list (ti::remove-properties (match-string 1)))
2141 ;; Nope, some additional PGP header
2143 (ti::nconc info-list (ti::remove-properties (ti::read-current-line)))))
2144 ;; Because there is already one while loop that says fwd-line,
2145 ;; we don't want to go furher if it stopped us.
2146 (if (looking-at (concat p-re "\\|^\t+"))
2152 (setq ret (list (cons beg end) info-list sig-list)))
2156 ;;; ----------------------------------------------------------------------
2158 (defun ti::mail-pgp-signature-header-info-v3xx ()
2159 "Return signature information from X-pgp v3.xx headers.
2163 (\"Version: x.x.x\" \"Charset: xxxx\" ...)
2164 (signature-string sig-string ..))"
2165 (let ((field (ti::remove-properties (ti::mail-get-field "X-Pgp-signed")))
2171 (setq list (ti::mail-mime-parse-header field 'downcase))
2172 ;; Push adds to the front of list, so beware order of elements
2173 (if (setq elt (assoc "signature" list))
2174 (setq sig-list (cdr elt)))
2175 (if (setq elt (assoc "comment" list))
2176 (push (concat "Comment: " (car (cdr elt))) info-list))
2177 (if (setq elt (assoc "charset" list))
2178 (push (concat "Charset: " (car (cdr elt))) info-list))
2179 (if (setq elt (assoc "version" list))
2180 (push (concat "Version: " (car (cdr elt))) info-list ))
2182 (list (cons nil nil) info-list sig-list)))))
2184 ;;; ----------------------------------------------------------------------
2186 (defun ti::mail-pgp-signature-header-info ()
2187 "Return X-pgp header info if X-Pgp header exist."
2188 (if (ti::mail-pgp-v3xx-p)
2189 (ti::mail-pgp-signature-header-info-v3xx)
2190 (ti::mail-pgp-sig-header-info-v2xx)))
2192 ;;; ----------------------------------------------------------------------
2194 (defun ti::mail-mime-parse-header (header-string &optional downcase)
2195 "Parse Variable=value HEADER-STRING like and optionally DOWNCASE keywords.
2197 Header-this: var1=value2; var2= val2; var3=\"starts here \"
2198 \" continues here\"; var4= v1,v2,v3;
2200 The VAL returned is different for continued string. It is a list of
2201 individual parts in the parsed. In this case the whole returned value
2206 (var3 . (\"starts here \" \" continues here\"))
2207 (var4 . (\" v1,v2,v3\")))
2210 ((var . VAL) (var . VAL) ..)"
2211 (let ((tag-re "^[ \t]*\\([^ \t\n]+\\)[ \t\"]*=")
2212 (val-re "[ \t\"]*\\([^\n\"]+\\)")
2213 (buffer (ti::temp-buffer "*tmp*" 'clear))
2217 (with-current-buffer buffer
2218 (insert header-string)
2219 ;; put into same line
2220 (ti::pmin) (ti::buffer-replace-regexp "[ \t]*;[ \t]*" 0 "\n")
2221 ;; Okay now it's in canonical format. First
2222 ;; pick up signature, then delete it and parse other fields.
2226 (while (re-search-forward tag-re nil t)
2227 (setq name (match-string 1) val nil)
2229 ((looking-at val-re) ;VALUE at the same line
2230 (ti::nconc val (match-string 1))
2234 (while (progn (forward-line 1)
2235 (looking-at val-re))
2236 (ti::nconc val (match-string 1)))))
2238 (setq name (downcase name)))
2239 (push (cons name val) ret)))
2243 ;;{{{ PGP public key
2245 ;;; ........................................................ &pgp-pkey ...
2247 ;;; ----------------------------------------------------------------------
2249 (defun ti::mail-pgp-pkey-read (&optional raw kill-file)
2250 "Read public key block from current point forward. Point is moved.
2254 RAW If non-nil, return only raw public key block.
2255 KILL-FILE if non-nil, kill temporary file after statement
2256 'Key extracted to file ...' Once the file is killed the
2257 message will be removed from buffer."
2262 ;; No temp files are left on disk
2263 ;; Remove also the file message from buffer before we read the
2266 ;; Extracting from key ring: '/users/xxx/.pgp/pubring.pgp',\
2269 ;; Key for user ID: <xxx@some.fi>
2270 ;; 512-bit key, key ID 8125CAAA, created 1997/06/05
2272 ;; -----BEGIN PGP PUBLIC KEY BLOCK-----
2273 (when (and kill-file
2274 (re-search-forward "Key extracted to file.*'\\(.*\\)'" nil t))
2275 (setq file (match-string 1))
2276 (ti::buffer-kill-line)
2277 (ti::file-delete-safe file))
2279 (when (re-search-forward (ti::mail-pgp-pkey-begin-line) nil t)
2280 (re-search-backward "Key for user ID:") (beginning-of-line)
2282 (re-search-forward "^-----BEGIN")
2283 (beginning-of-line))
2285 (when (ti::mail-pgp-re-search 'pkeye 'move)
2289 (setq ret (buffer-substring beg end))))
2295 ;;; ----------------------------------------------------------------------
2297 (defun ti::mail-pgpr-close ()
2298 "Close reply block by adding '**' to the end.
2299 If there already is '**', do nothing."
2302 ;; Remailers need "**" at the end of encrypted block
2303 (if (not (re-search-backward "^\\*\\*" nil t))
2304 (insert "\n**\n"))))
2306 ;;; ----------------------------------------------------------------------
2308 (defun ti::mail-pgpr-anonymize-headers (mode &optional no-ins arg1 arg2 hash)
2309 "Destroy header information according to mode and move it to message body.
2310 This function does nothing if first line is not header.
2314 MODE 'move-to-body moves, all headers to body
2315 'move-to-body-maybe, all headers to body only if
2316 there is not already hash marks.
2317 arg1 is used for subject defaults to 'dummy'
2318 arg2 is used for organisation defaults to 'dummy'
2320 NO-INS Do not insert the hash headers into body, but return them
2323 ARG1 ARG2 used by MODE
2325 HASH Use hash marks string other that \"##\"
2330 (let ((hlist '("In-reply-to"
2340 (setq hash (or hash "##")
2341 arg1 (or arg1 empty)
2342 arg2 (or arg2 empty))
2344 (when (ti::mail-mail-p)
2346 ((memq mode '(move-to-body move-to-body-maybe))
2347 ;; First check if hash mark is already there
2348 ;; If mode is "maybe" we don't add new headers.
2350 ;; The regexp matches to the end of line, because you may have
2351 ;; quoted the message
2354 ;; jerry> Subject: this here
2356 (unless (and (eq mode 'move-to-body-maybe)
2357 (re-search-forward (concat hash "[ \t]*$") nil t))
2360 (setq str (ti::mail-get-field elt))
2361 (when (and str (not (string= empty str)))
2362 (setq elt (format "%s: %s\n" elt str))
2364 ;; so that we can match against this later
2366 (setq full-string (concat full-string elt))))
2367 (ti::mail-text-start 'move)
2369 (setq ret list done t)
2371 ;; Remailer hash mark
2372 (insert hash "\n"))))
2373 ;; Anonymize some headers
2375 (ti::mail-kill-field "^subject" arg1))
2377 (ti::mail-kill-field "^organization" arg2))
2378 (when (and done (null no-ins))
2380 ;; Copy headers inside message
2383 (error "Invalid mode [%s]" mode)))))
2386 ;;; ----------------------------------------------------------------------
2388 (defun ti::mail-pgpr-reply-type (property-string)
2389 "Return remailer reply block type from PROPERTY-STRING.
2390 The 'post' type is not checked, because it relates to Usenet
2391 and can be mixed with other types."
2392 (if (string-match "cpunk\\|eric\\|penet" property-string)
2393 (match-string 0 property-string)))
2395 ;;; ----------------------------------------------------------------------
2396 ;;; used to be: cpunk Request-Remailing-To
2397 ;;; but nowadays instructions say "Anon-To"
2399 (defun ti::mail-pgpr-block (mode &optional type email key latent)
2400 "Return remailer header string defined by mode.
2401 be sure to have <> in the email, which defaults to `user-mail-address'.
2405 MODE 'epgp -- return encrypted pgp tag.
2406 'post -- return simple Newsgroup post block. 'email'
2407 contains the address of post remailer.
2408 If there is not enough
2409 parameters, say for 'tk, the previous one is used: 't
2414 post Anon-Post-To Usenet
2416 EMAIL Parameter for type
2417 KEY Parameter for type
2418 LATENT Parameter for type"
2421 ((string= type "cpunk") "Anon-To")
2422 ((string= type "eric") "Anon-To")
2423 ((string= type "penet") "X-Anon-To")
2424 ((string= type "post") "Anon-Post-To")
2425 ((memq mode '(epgp post))) ;Ok; skip
2426 ((error "Unknown type '%s'" type)))))
2427 (setq email (or email user-mail-address))
2430 "::\nEncrypted: PGP\n\n")
2434 "Anon-Post-To: " (or email (error "invalid args.")) "\n"
2436 ((and (stringp email) (stringp key) (stringp latent))
2437 (format "::\n%s: %s\nEncrypt-Key: %s\nLatent-Time: %s\n"
2438 reply email key latent))
2439 ((and (stringp email) (stringp latent))
2440 (format "::\n%s: %s\nLatent-Time: %s\n" reply email latent))
2441 ((and (stringp email) (stringp key))
2442 (format "::\n%s: %s\nEncrypt-Key: %s\n" reply email key))
2443 ((or (stringp email))
2444 (format "::\n%s: %s\n" reply email))
2446 (error "Wrong args '%s' '%s'" mode type )))))
2448 ;;; ----------------------------------------------------------------------
2450 (defun ti::mail-pgpr-reply-block (pgp-email)
2451 "Return reply block header.
2452 Should be inserted just before PGP crypted message to PGP-EMAIL."
2453 (format "Reply-Block:\n::\nAnon-To: %s\n\n" pgp-email))
2455 ;;; ----------------------------------------------------------------------
2457 (defun ti::mail-pgpr-parse-levien-list (&optional buffer control-list)
2458 "Parse remailer list finger <remailer-list@kiwi.cs.berkeley.edu>.
2459 The parsing starts from current point forward.
2463 BUFFER defaults to current buffer
2464 CONTROL-LIST '(remailer-alias (prop-no-list) [(prop-add-list)])
2465 This control list says 'if REGEXP matches the
2466 email address, remove all properties listed in
2467 prop-no-list and add all properties listed in
2470 So, if you're sure that the levien-list has some
2471 faulty entries, e.g. say remailer@replay.com doesn't
2472 have feature 'ek' although levien list contains that,
2473 your control-list is like this. The ek property
2474 is removed even if the list says otherwise.
2476 '(\"replay\" '(\"ek\"))
2480 '((alias remailer property_string (property property ...))
2481 (alias remailer property_string (p p p ..)))
2483 The properties are sorted: cpunk mix pgp..."
2485 "^[ \t]*$remailer{[\"']\\(.*\\)[\"']}.*=[ \t]*[\"']"
2486 "<\\(.*\\)>[ \t]+\\(.*\\)[\"']"))
2494 ;; The list is in Perl hash array format in case you're interested...
2495 (with-current-buffer (or buffer (current-buffer))
2496 (while (re-search-forward re nil t)
2497 (setq a (match-string 1)
2500 (setq blocks (split-string p))
2501 (setq blocks (sort blocks 'string<))
2502 (when (and control-list
2503 (setq elem (assoc a control-list)))
2504 (setq list (nth 1 elem))
2506 (setq blocks (delete elt blocks)))
2507 (setq list (nth 2 elem))
2508 (dolist (elt list) (push elt blocks))
2509 ;; We used this now, remove from list
2510 (setq control-list (delete elem control-list))
2511 (setq p (mapconcat 'concat blocks " ")))
2512 ;; features In alphabetic order
2513 (setq p (mapconcat 'concat blocks " "))
2514 (push (list a r p blocks) ret)))
2519 ;;{{{ email addresses
2521 ;;; ----------------------------------------------------------------------
2523 (defun ti::mail-email-make-anti-spam-address (email)
2524 "Make an anti-spam address from EMAIL."
2566 (elt (shuffle-vector base ) 1)
2568 (elt (shuffle-vector add) 1))
2570 (elt (shuffle-vector add) 1)
2572 (elt (shuffle-vector base ) 1))))
2573 (this (elt (shuffle-vector vec) 0))
2576 (string-match "\\(.*\\)@\\(.*\\)" email)
2577 (setq login (match-string 1 email)
2578 domain (match-string 2 email))
2581 (if (zerop (randij 0 1))
2582 (concat "." this "@")
2583 (concat "@" this "."))
2586 ;;; ----------------------------------------------------------------------
2588 (defun ti::mail-email-domain (string)
2589 "Return only the top level domain name from email STRING.
2590 xx.yy..domain.com --> domain.com
2591 xx.domain.co.uk --> domain.co.uk"
2593 ;; This match tries to catch those domains that don't have 3 parts,
2597 ;; We expect this part to be longer than 2 characters
2599 ((string-match "[^.][^.][^.]+\\.\\(..\\|...\\)$" string)
2600 (match-string 0 string))
2601 ;; This is domain that requires 3 parts: co.uk or au jp
2602 ((string-match "[^.]+\\.[^.]+\\.\\(..\\|...\\)$" string)
2603 (match-string 0 string))
2604 ((string-match "[^@]+$" string)
2605 (match-string 0 string))))
2607 ;;; ----------------------------------------------------------------------
2609 (defun ti::mail-email-domain-canonilize (list)
2610 "Canonilize list of addresses to top level domain names only
2611 Eg: '(\"aa.foo.com\" \"bb.foo.com\") --> '(\"foo.com\")"
2615 (setq domain (ti::mail-email-domain elt))
2616 (add-to-list 'ret domain))
2619 ;;; ----------------------------------------------------------------------
2621 (defun ti::mail-email-find-region (&optional beg end no-dupes)
2622 "Find all email addresses within region BEG END (defaults to buffer).
2623 The email addresses must contain @. Surrounding <> characters are removed.
2627 BEG region start; defaults to `point-min'
2628 END region end; defaults to `point-min'
2629 NO-DUPES flag; if non-nil then cache only unique entries."
2633 (setq beg (or beg (point-min))
2634 end (or end (point-max)))
2635 (ti::keep-lower-order beg end)
2637 ;; Intangible text property case:
2638 ;; - When you do a limited search and cursor land somewhere in
2639 ;; intangible char, it immediately slides to next char
2640 ;; position. Like if you'd do
2642 ;; (progn (goto-char 10) (point))
2645 ;; This is not suprise, if point 10 had intangible text until
2646 ;; 19th pos. If there were no intangible text in point 10,
2647 ;; the result would be expected 10.
2648 (while (and (<= (point) end) ;; intangible test
2650 "[^ '\",:<\t\n(]+@[^ '\">:,\t\n)]+"
2653 (setq elt (ti::remove-properties (match-string 0)))
2655 (if (and (stringp elt)
2656 (or (or (null no-dupes)
2657 (not (member elt list)))))
2661 ;;; ----------------------------------------------------------------------
2663 (defun ti::mail-email-from-string (string)
2664 "Return list of email addresses from STRING.
2665 The addresses must have @ character. Surrounding <> characters are removed.
2666 If STRING is nil this function does nothing."
2667 ;; Using buffer is faster that reading string
2671 (ti::mail-email-find-region))))
2676 ;;; ......................................................... &parsing ...
2678 ;;; ----------------------------------------------------------------------
2679 ;;; (ti::mail-test-parse-name)
2681 (defun ti::mail-test-parse-name ()
2682 "This is a test function, do not call from programs.
2684 Because the `ti::mail-parse-name' is quite complicated,
2685 and slightest modification may render it, this functions tests
2686 that the old functionality is preserved in spite of changes."
2693 '("<jdoe@examole.com> (Finland, pgp id 512/47141D35)"
2694 "(Rune Juntti[FRONTEC Pajala]) <jdoe@example.se>"
2695 "shahramn@wv.mentorg.com (jdoe@example.com)"
2696 "(jdoe@example.com)"
2697 "Jerome Santini <doe@this-example.here.com>"
2698 "jdoe@example.com (Harry Halladay - EDS St. Louis)"
2699 "jdoe@example.com (Ake Stenhoff TM/PMD 83442 3003)"
2700 "CEO-executive this here jdoe@example.com"
2701 "JDOE <\"VAX::SOME@example.com\""
2702 "\"VAX::LOGIN\"@example.com"
2703 "john.doe@example.com"
2704 "John=Doe%aoa.rdt%OS.DC@example.com"
2705 "jdoe@example.com (John Doe)"
2706 "\"/G=Name/S=Surname/OU=comlab/O=oxford/PRMD=UK.AC/ADMD= /C=GB/\"@example.fi\""
2707 "\"wayne (w.d.) bell\" <jdoe@example>"
2708 "John doe <example@example.com>"
2709 "\"Joseph B. Ottinger\" <j.doe@example.com>"
2710 "\"Name Foo puh. 111 600\" <LOGIN@example.com>"
2711 "\"stephane (s.) boucher\" <jdoe@example.com>"
2712 "jdoe@example.com (J.D \"John\" Doe)"
2713 "jd@example-com (J.D doe)"
2714 "doe@example.com \(John Doe\)"
2715 "jdoe@example.com \(John D. Doe\)"
2716 "\"J. doe Ph.d \" jdoe@john.doe.example.com"
2717 "\"John D. Doe\" <foo@example.com>"))
2720 (setq stat (ti::mail-parse-name n))
2721 (setq e1 (nth 0 stat)) (setq e2 (nth 1 stat))
2722 (read-from-minibuffer (concat "TEST>>" e1 "," e2 "<")))))
2724 ;;; ----------------------------------------------------------------------
2725 ;;; (ti::mail-t-parse-name)
2727 (defun ti::mail-parse-name (line)
2728 "Try to parse various formats of 'From:' fields.
2729 Supposes that the 'From:' keyword is removed from the LINE.
2732 list '(firstname surname)
2733 nil if cannot parse both"
2734 (let* ((re-A "[-a-zA-Z0-9.{|]")
2735 (re-AG (concat "\\(" re-A "+\\)"))
2737 ;; 'From: Mr-CEO John Doe <jdoe@example.com'
2738 (fs-re2 (concat re-AG " +" re-AG))
2740 ;; 'USER <\"CLUSTER::VAX\@site.cm\"'
2741 (fs-vax (concat "^" re-AG "[ \t<\"]+[A-Z]+::" re-AG))
2743 ;; '\"CLUSTER::LOGIN\"@example.com'
2744 ;; This is incomplete Name, it does not contain NAMES at all, but
2745 ;; we consider mail name as surname. The first group-RE is dummy.
2746 (fs-vax2 (concat re-AG "::" re-AG))
2748 ;; 'Job.Ganzevoort@cwi.nl', where person's name is complete
2750 (fs-fse (concat re-AG "\\." re-AG "@" ))
2752 ;; matches gateway-type addresses
2753 ;; 'Marla=Bush%aoa.rdt%OS.DC@Ban-Gate.AoA.DHHS.EDU'
2754 (gtw-re1 (concat re-AG "=" re-AG "%" ))
2756 (q-no-re ti:mail-parse-name-not-accept)
2758 (mail (or (ti::mail-parse-email line) ""))
2759 (account (if (= 2 (length mail))
2761 "#@$@#$@#$")) ;just some dummy
2780 (setq D D)) ;XE 19.14 ByteComp silencer, no-op
2784 ;; It's most important that the match test are made IN THIS ORDER
2785 ;; - Quote test cannot precede vax name test.
2786 ;; - Try most restrictive first.
2788 ;; ..............................................................
2789 ;; VAX is identified by "::" marks
2791 (when (string-match "::" line)
2792 (setq list (ti::mail-get-2re fs-vax line))
2793 (when (not (string= "" (nth 0 list)))
2796 (setq list (ti::mail-get-2re fs-vax2 line))
2797 (when (not (string= "" (nth 0 list)))
2801 ;; ............................................................
2802 ;; Try gateway addresses, rare, but seen in net still
2804 (when (string-match "%" line)
2805 (setq list (ti::mail-get-2re gtw-re1 line))
2806 (when (not (string= "" (nth 0 list)))
2812 (when (string-match "/G=\\(.*\\)/S=\\([^/]+\\).*C=" line)
2813 (setq fn (match-string 1 line)
2814 sn (match-string 2 line))
2816 (setq list (list fn sn) D "gateX400")
2819 ;; .................................................................
2820 ;; foo.bar@example.com
2822 (when (string-match fs-fse line)
2823 (setq list (ti::mail-get-2re fs-fse line))
2824 (when (not (string= "" (nth 0 list)))
2825 (setq D "mike.gordon")
2828 ;; ............................................................
2829 ;; And the rest , is there paren or "" somewhere ?
2832 ;; If this is a full email string Joe@foo.com
2833 ;; then get only the first part.
2835 (when (and (setq tmp (ti::string-match "^\\([^ \t]+\\)[@%][^ \t]+$" 1 line))
2836 (setq tmp (ti::string-match re-AG 1 tmp)))
2838 (setq list (list tmp ""))
2841 ;; - if we get multiple match "stephane (s.) boucher" ,
2842 ;; (L.G. \"Ted\" Stern) , pick the one that's longer.
2844 (if (string-match "\"\\(.*\\)\"" line)
2845 (setq beg1 (match-beginning 1) end1 (match-end 1)))
2847 (if (string-match "[(]\\(.*\\)[)]" line)
2848 (setq beg2 (match-beginning 1) end2 (match-end 1)))
2852 (if (> (- end1 beg1) (- end2 beg2))
2853 (setq beg beg1 end end1)
2854 (setq beg beg2 end end2)))
2856 (setq beg beg1 end end1))
2858 (setq beg beg2 end end2)))
2860 ;; ... ... ... ... ... ... ... ... ... ... ... ... ...
2864 ;; - Get list of words into W
2865 ;; - Someone wrote M. "Mack" Monroe, so the " is included
2866 ;; in words separate list
2867 ;; - The latter picks only NON-ABBREVIATED names, non-phones..
2868 ;; M. "Mack" Monroe --> Mack Monroe
2871 (setq pick (substring line beg end))
2872 (setq w (split-string pick "[][@%. \"]+"))
2875 ;;; (ti::d! "w-1" w)
2877 (let ((case-fold-search nil)) ;case is important !!
2878 (setq w ;returned word list
2884 (not (string-match arg elt))))
2887 ;;; (ti::d! "w-2" w)
2890 ((> (length w) 2) ;too much abbrev names
2891 ;; pick first and account or last word
2893 ;;; (setq W w AC account)
2895 (setq w1 (nth 0 w) w2 (nth (1-(length w)) w) )
2897 (setq tmp (ti::list-find
2901 (string-match elt arg)))))
2903 (if tmp ;account name found
2906 (setq list (list w1 w2)))
2909 (setq w1 (nth 0 w) w2 (nth 1 w))
2910 (setq list (list w1 w2)))
2921 ;; .................................................................
2923 (setq list (ti::mail-get-2re fs-re2 line))
2924 (when (not (string= "" (nth 0 list)))
2926 (throw 'found t))) ;; Catch end
2928 ;;; (ti::d! "parsed" D list)
2930 ;; what should we return ?
2931 (if (and (string= (nth 0 list) "")
2932 (string= (nth 1 list) ""))
2936 ;;; ----------------------------------------------------------------------
2938 (defun ti::mail-parse-email (line)
2939 "Try to parse various formats of 'From:' field from LINE.
2940 Input is full LINE, possibly containing 'From' keyword.
2944 list '(usrname site)
2945 nil if cannot parse."
2950 ;; '.' is for firstname & surname combination
2951 ;; '=' is for gateway form
2952 ;; '|{' are scandinavian characters in name
2953 ;; '+' Believe or not, but I just saw account name like
2954 ;; "Stephen M. Lacy" <sl31+@andrew.cmu.edu>
2956 (A "[-a-zA-Z|{0-9_=.+]+") ; alphabet
2957 (As "[-a-zA-Z0-9.%]+") ; site name
2959 ;; Note that username can have scandinavian {| marks
2961 ;; o Simon.Marshall@mail.bar.foo.fi (Simon Marshall)
2962 (re1 (concat "\\(" A "\\)@\\(" As "\\)" ))
2964 ;; Marla=Bush%aoa.rdt%OS.DC@Ban-Gate.AoA.DHHS.EDU
2965 (re2 (concat "\\(" A "\\)\\(%" As "\\)" ))
2967 ;; VAX address <"TNCLUS::TSYVANEN"@mailer.foo.fi>
2968 (re-vax (concat "\\(\"" A "::" A "\"\\)@\\(" As "\\)" ))
2971 ;; "/G=Jamie/S=Lokier/OU=comlab/O=oxford/PRMD=UK.AC...
2973 (concat "/G=\\([^/]+\\)/S=\\([^/]+\\)" ;fn sn
2974 "/OU=\\([^/]+\\)/O=\\([^/]+\\)"
2975 "/PRMD=\\([^/]+\\)")))
2977 ;;; (setq LINE line RE re-x400)
2979 (if (null (string-match re-x400 line)) nil
2980 (setq account (concat (match-string 1 line) "." (match-string 2 line)))
2981 (setq site (concat (match-string 3 line) "." (match-string 4 line)))
2983 ;; Now switch the last items PRMD=UK.AC --> ac.uk
2984 (setq tmp (match-string 5 line))
2985 (setq tmp (split-string tmp "[.]"))
2986 (setq site (downcase (concat site "." (nth 1 tmp) "." (nth 0 tmp))))
2987 (setq em (list account site))
2990 (setq em (ti::mail-get-2re re-x400 line))
2991 (if (not (string= "" (nth 0 em))) (throw 'found t))
2993 (setq em (ti::mail-get-2re re1 line))
2994 (if (not (string= "" (nth 0 em))) (throw 'found t))
2996 (setq em (ti::mail-get-2re re2 line))
2997 (if (not (string= "" (nth 0 em))) (throw 'found t))
2999 (setq em (ti::mail-get-2re re-vax line))
3000 (if (not (string= "" (nth 0 em))) (throw 'found t)))
3002 (if (< (length (nth 0 em)) 1)
3006 ;;; ----------------------------------------------------------------------
3008 (defun ti::mail-parse-received-regexp-list ()
3009 "Return list of regexps that match `Received:' header content.
3010 The Return ed list content is
3016 Where the number indicated how many submatches can be read. E.g. Number
3017 3 means, 3 submatches."
3018 (let* ((from "[ \t]+from")
3021 (W "[^][(){} \t\n]+") ;;word
3022 (word (concat "\\(" W "\\)")) ;;capturing word
3023 (S "[[({]+") ;;start
3026 ;; mail.compuserve.com (mail.compuserve.com (209.5.81.86))
3027 ;; mail.msss.v.com [atl.asd.com [234.454.54]]
3036 ;; Received: from [209.151.131.35] (HELO mx04.hotmail.com)
3037 ;; by elysium.ca (CommuniGate Pro SMTP 3.5)
3041 spc+ S word E ;; from [209.151.131.35]
3042 spc+ S W spc+ word E ;; (HELO mx04.hotmail.com)
3043 spc+ "by" spc+ word))
3045 ;; from hdn86-021.hil.compuserve.com(206.175.97.21) by
3053 ;; Propably faked received header?
3055 ;; from usinet cziegle (1Cust144.tnt1.coeur-dalene.id.da.uu.net
3056 ;; [208.254.107.144]) by ns.peace1.co.jp
3065 ;; Received: from usa.net - 206.133.11.158 by
3066 ;; ciudad.com.ar with Microsoft SMTPSVC; Mon, 2 Feb 1998 21:03:25
3071 spc+ word spc+ "by"))
3073 ;; Received: from foo by relay1.UU.NET with SMTP
3074 ;; (peer crosschecked as: 1Cust185.tnt10.nyc3.da.uu.net
3075 ;; [153.37.131.185])
3080 spc word spc "with"))
3082 ;; from [206.102.180.52] by springfield.k12.il.us with ESMTP
3086 spc S word E spc "by"
3087 spc word spc "with"))
3089 ;; Received: by SERVER02 with Internet Mail Service (5.5.2650.21)
3090 ;; id <FVLHVM1Q>; Thu, 28 Feb 2002 16:26:29 -0500
3093 (concat spc+ "by" spc+ W spc+ "with" spc+ W spc+ W spc+ W
3096 ;; from papaguena.upc.es by rita.upc.es
3098 (re-word12 (concat from spc word spc "by" )))
3100 (list 3 (list re-word31
3102 (list 2 (list re-word2a
3107 (list 1 (list re-word11
3110 ;;; ----------------------------------------------------------------------
3112 (defun ti::mail-parse-received-line (regexp-list)
3113 "Parse all `Received:' IPs from current line with REGEXP-LIST.
3114 The point must be placed just after the colon in header:
3118 The -!- indicates the location of point."
3121 (dolist (elt regexp-list)
3122 (multiple-value-bind (submatch-max regexp-list)
3124 (dolist (regexp regexp-list)
3125 (when (looking-at regexp)
3126 (dotimes (count submatch-max) ;; starts counting from 0
3127 (push (match-string (1+ count)) candidates))
3129 (throw 'done t))))))
3130 (nreverse candidates)))
3132 ;;; ----------------------------------------------------------------------
3134 (defun ti::mail-parse-received-string-smtp (string)
3135 "Parse SMTP field from 'Received:' STRING."
3136 ;; from 111.npgco.com (HELO NAZ-AZPIRE1) (24.121.15.77)
3139 "\\<from[ \t\r\n]+[^ \t\r\n]+[ \t\r\n]+"
3141 "([^()]+)" ;; First paren, required
3142 "\\([ \t\r\n]+" ;; Second, optional
3144 "\\)") ;; END capture
3146 (let* ((str (match-string 1 string))
3149 (if (string-match " " str)
3150 (setq list (split-string str)))
3152 (push (replace-regexp-in-string "\\[\\|\\]\\|[()\r\n]" "" elt)
3156 ;;; ----------------------------------------------------------------------
3158 (defsubst ti::mail-parse-received-string-clean (string)
3159 "Remove () and newlines from STRING."
3160 (replace-regexp-in-string "[()\r\n]" "" string))
3162 ;;; ----------------------------------------------------------------------
3164 (defsubst ti::mail-parse-received-string-from (string)
3165 "Parse 'from' field from 'Received:' STRING."
3166 (when (string-match "\\<from[ \t\r\n]+\\([^ \t\r\n]+\\)" string)
3167 ;; from cm-24-121-15-77.flagstaff.az.npgco.com (HELO NAZ-AZPIRE1)
3168 (match-string 1 string)))
3170 ;;; ----------------------------------------------------------------------
3172 (defsubst ti::mail-parse-received-string-by (string)
3173 "Parse 'from' field from 'Received:' STRING."
3174 (when (string-match "\\<by[ \t\r\n]+\\([^ \t\r\n]+\\)" string)
3175 (match-string 1 string)))
3177 ;;; ----------------------------------------------------------------------
3179 (defsubst ti::mail-parse-received-string-smtp-id (string)
3180 "Parse 'from' field from 'Received:' STRING."
3183 "[ \t\r\n]+id[ \t\r\n]+\\([^ ;\t\r\n]+\\)" string)
3184 (match-string 1 string))))
3186 ;;; ----------------------------------------------------------------------
3188 (defsubst ti::mail-parse-received-string-for (string)
3189 "Parse 'from' field from 'Received:' STRING."
3190 (when (string-match "\\<for[ \t\r\n]+\\([^ ;\t\r\n]+\\)" string)
3191 (match-string 1 string)))
3193 ;;; ----------------------------------------------------------------------
3195 (defsubst ti::mail-parse-received-string-date (string)
3196 "Parse 'from' field from 'Received:' STRING."
3198 "^.+;[ \t\r\n]+\\(.+[^ \t\r\n]\\)" string)
3199 (match-string 1 string)))
3201 ;;; ----------------------------------------------------------------------
3202 ;;; (ti::mail-parse-date-string "Thu, 18 Jul 1996 12:18:06 -0600")
3203 ;;; (ti::mail-parse-date-string "21 Aug 2003 20:41:15 -0000")
3204 (defun ti::mail-parse-date-string (date)
3205 "Parse DATE notation.
3206 Recognized format are:
3208 Thu, 18 Jul 1996 12:18:06 -0600
3209 21 Aug 2003 20:41:15 -0000
3211 The timezone value is optional.
3218 mm ;; numeric string, like \"07\" for \"Jul\"
3226 (concat "^[ \t\r\n]*"
3227 "\\([A-Z]..\\),?[ \t\r\n]+"
3228 "\\([0-9]+\\)[ \t\r\n]+"
3229 "\\([A-Z]..\\)[ \t\r\n]+"
3230 "\\([0-9][0-9][0-9][0-9]\\)[ \t\r\n]+"
3237 (match-string 1 date)
3238 (format "%02d" (string-to-int (match-string 2 date)))
3239 (match-string 3 date)
3241 (ti::month-to-number (match-string 3 date)))
3242 (match-string 4 date)
3243 (match-string 5 date)
3244 (match-string 6 date)
3245 (match-string 7 date)
3246 (match-string 8 date)))
3250 "\\([0-9][0-9]?\\)[ \t\r\n]+"
3251 "\\([A-Z]..\\)[ \t\r\n]+"
3252 "\\([0-9][0-9][0-9][0-9]\\)[ \t\r\n]+"
3260 (match-string 1 date)
3261 (match-string 2 date)
3263 (ti::month-to-number (match-string 2 date)))
3264 (match-string 3 date)
3265 (match-string 4 date)
3266 (match-string 5 date)
3267 (match-string 6 date)
3268 (match-string 7 date)))))
3270 ;;; ----------------------------------------------------------------------
3271 ;;; (ti::mail-parse-date-string-iso8601 "Thu, 18 Jul 1996 12:18:06 -0600")
3272 (defun ti::mail-parse-date-string-iso8601 (date &optional tz)
3273 "Parse DATE. See supported values in `ti::mail-parse-date-string'.
3274 Return ISO 8601 date
3278 If TZ is non-nil, add timezone information to the end."
3280 (multiple-value-bind
3288 (ti::mail-parse-date-string date)
3289 (format "%s-%s-%s %s:%s:%s%s"
3290 yyyy mm dd HH MM SS (if tzone
3294 ;;; ----------------------------------------------------------------------
3296 (defun ti::mail-parse-received-string (string)
3297 "Parse 'Received:' Header STRING.
3300 Received: from host1 (host2 [ww.xx.yy.zz]) by host3
3301 (8.7.5/8.7.3) with SMTP id MAA04298; Thu, 18 Jul 1996 12:18:06 -0600
3306 (smtp . (HOST2 ...))
3312 The `cdr' of a key may be nil if no value was found.
3316 `ti::with-mail-received-heade'."
3318 (cons 'from (ti::mail-parse-received-string-from string))
3319 (cons 'smtp (ti::mail-parse-received-string-smtp string))
3320 (cons 'by (ti::mail-parse-received-string-by string))
3321 (cons 'smtp-id (ti::mail-parse-received-string-smtp-id string))
3322 (cons 'for (ti::mail-parse-received-string-for string))
3323 (cons 'date (ti::mail-parse-received-string-date string))))
3325 ;;; ----------------------------------------------------------------------
3327 (defun ti::mail-parse-received (&optional not-matching no-dupes)
3328 "Search all 'Receive:' fields and read site names followed by 'from' 'by'.
3329 Duplicate entries are not added.
3331 Point must be at the beginning of headers to search, and
3334 It is possible to can this function to find out from where the mail
3335 originated and send complaint to postmasters of all those sites.
3339 NOT-MATCHING string, If read entry matches this regexp it is not included in
3341 NO-DUPES flag, if non-nil then do not include duplicate addresses.
3345 '((IP IP IP) (IP IP) ..) as they appear in Received fields.
3347 Received headers explained:
3349 Received: from host1 (host2 [ww.xx.yy.zz]) by host3
3350 (8.7.5/8.7.3) with SMTP id MAA04298; Thu, 18 Jul 1996 12:18:06 -0600
3352 This Shows four pieces of useful information (reading from back to front,
3353 in order of decreasing reliability):
3355 - The host that added the Received line (host3)
3356 - The IP address of the incoming SMTP connection (ww.xx.yy.zz)
3357 - The reverse-DNS lookup of that IP address (host2)
3358 - The name of the sender used in the SMTP HELO command at the
3363 Received: from mailhost.worldnet.att.net ([206.85.117.127])
3364 by mtigwc02.worldnet.att.net (post.office MTA v2.0 0613 )
3365 with SMTP id AAD8244; Sun, 23 Mar 1997 23:03:10 +0000
3366 Received: from mail.msss.v.com [atl.asd.com [234.454.54]]
3367 by mediabrokers.cobracomm.com (8.8.5/8.6.5) with
3368 SMTP id GAA07901 for <box17@mediabrokers.cobracomm.com>"
3369 (let* ((regexp-list (ti::mail-parse-received-regexp-list))
3375 (while (re-search-forward "^Received:" nil t)
3377 (setq candidates (ti::mail-parse-received-line regexp-list))
3379 (dolist (elt candidates)
3380 (when (and (stringp elt)
3381 (string-match "\\." elt) ;;from PAPAGUENA, require dot(.)
3382 ;; Is exclude in effect?
3383 (or (null not-matching)
3384 (not (string-match not-matching elt)))
3386 (not (member elt ip-all-list))
3388 ;; 1) mailhost@inet.com --> inet.com
3389 ;; 2) remove some garbage from string
3391 (setq elt (replace-regexp-in-string ".*@" "" elt))
3392 (setq elt (replace-regexp-in-string "[]()\n]" "" elt))
3397 (push elt ip-all-list)) ;Needed for duplicate checking
3404 ;;; ----------------------------------------------------------------------
3406 (put 'ti::with-mail-received-header 'edebug-form-spec '(body))
3407 (put 'ti::with-mail-received-header 'lisp-indent-function 1)
3408 (defmacro ti::with-mail-received-header (string &rest body)
3409 "With Mail 'received:' heading in STRING, run BODY.
3412 Received: from host1 (host2 [ww.xx.yy.zz]) by host3
3413 (8.7.5/8.7.3) with SMTP id MAA04298; Thu, 18 Jul 1996 12:18:06 -0600
3415 The following access variables are available within BODY:
3417 received-header-data
3419 smtp => '(host2 ww.xx.yy.zz)
3423 date => Thu, 18 Jul 1996 12:18:06 -0600
3427 Any of the variables may be nil, if no value found.
3431 See functions ti::mail-parse-received-string-*
3432 and `ti::mail-parse-received-string'."
3433 `(let ((received-header-data (ti::mail-parse-received-string ,string)))
3434 (symbol-macrolet ((from (cdr (assq 'from received-header-data)))
3435 (smtp (cdr (assq 'smtp received-header-data)))
3436 (by (cdr (assq 'by received-header-data)))
3437 (smtp-id (cdr (assq 'smtp-id received-header-data)))
3438 (for (cdr (assq 'for received-header-data)))
3439 (date (cdr (assq 'date received-header-data))))
3442 ;;; ----------------------------------------------------------------------
3444 (defun ti::mail-whois-parse-cleanup (string)
3445 "Remove indentation and extra whitescape from STRING."
3446 ;; Remove indentation
3447 (ti::string-remove-whitespace
3448 (replace-regexp-in-string
3450 (replace-regexp-in-string "[ \t][ \t]+" " " string))))
3452 ;;; ----------------------------------------------------------------------
3454 (defun ti::mail-whois-parse-paragraph (regexp &optional end-regexp)
3455 "Whois: Parse pragraph for the first REGEXP to END-REGEXP.
3456 See `ti::mail-whois-parse'."
3457 (when (re-search-forward regexp nil 'noerr)
3458 (let ((beg (match-beginning 0)))
3459 (if (null end-regexp)
3461 (re-search-forward end-regexp)
3462 (beginning-of-line))
3463 (ti::mail-whois-parse-cleanup
3464 (buffer-substring beg (1- (point)))))))
3466 ;;; ----------------------------------------------------------------------
3468 (defun ti::mail-whois-parse-referral ()
3469 "Parse referral if any. See `ti::mail-whois-parse'."
3470 (let ((point (point)))
3472 ((and (goto-char point)
3474 ;; Found a referral to example.com
3475 "^[ \t]*Found.*referral to \\([^ \t\r\n]+[a-z]\\)"
3478 ((and (goto-char point)
3480 ;; Referral URL: http://example.com
3481 "^[ \t]*referral[ \t]+URL:[ \]*\\([^ \t\r\n]+\\)"
3483 (match-string 1)))))
3485 ;;; ----------------------------------------------------------------------
3487 (defun ti::mail-whois-parse-email ()
3488 "Whois: Parse unique email addresses from buffer.
3489 See `ti::mail-whois-parse'."
3490 ;; mailto:abuse@foo.com
3491 ;; trouble: Spam: <mailto:abuse@foo.com>
3492 ;; changed: 20030912 <migration@foo.com>
3496 "\\|\\(mailto\\|changed\\|updated\\):"
3497 "\\|\\<[0-9]+\\>"))<
3503 (while (re-search-forward
3506 "\\([^/,;<> \t\r\n]+@[^/,;<> \t\r\n]+\\)")
3508 ;; There is only one email at a line
3510 (replace-regexp-in-string
3513 (unless (member email seen)
3515 (setq line (ti::buffer-read-line))
3516 ;; Remove that email from it
3517 (when (setq desc (replace-regexp-in-string
3518 (regexp-quote email) "" line))
3520 (ti::string-remove-whitespace
3521 (replace-regexp-in-string
3523 (replace-regexp-in-string
3525 (replace-regexp-in-string
3526 "[ \t][ \t]+" " " desc))))))
3542 ;;; ----------------------------------------------------------------------
3544 (defsubst ti::mail-whois-parse-paragraph-end-condition ()
3545 "Whois parse. See `ti::mail-whois-parse'."
3547 "^[ \t]*\\(.+:[ \t]*[\r\n]"
3549 "\\|.*servers in listed order\\)"))
3551 ;;; ----------------------------------------------------------------------
3553 (defun ti::mail-whois-parse-registrant-1 ()
3554 "See `ti::mail-whois-parse-registrant'."
3555 (ti::mail-whois-parse-paragraph
3556 "^[ \t]*Registra\\(r\\|nt\\):.*[\r\n]+[ \t]*"
3557 (ti::mail-whois-parse-paragraph-end-condition)))
3559 ;;; ----------------------------------------------------------------------
3561 (defun ti::mail-whois-parse-registrant-organization ()
3562 "See `ti::mail-whois-parse-registrant'."
3563 (ti::mail-whois-parse-paragraph
3564 "^[ \t]*Organi[zs]ation:[ \t]*[\r\n]+[ \t]*"
3565 (ti::mail-whois-parse-paragraph-end-condition)))
3567 ;;; ----------------------------------------------------------------------
3569 (defun ti::mail-whois-parse-registrant-organization-2 ()
3570 "See `ti::mail-whois-parse-registrant'."
3571 ;; OrgName: AT&T WorldNet Services
3573 ;; Address: 400 Interpace Parkway
3576 ;; PostalCode: 07054
3581 ;; # ARIN WHOIS database, last updated 2003-08-25 19:15
3582 ;; # Enter ? for additional hints on searching ARIN's WHOIS database.
3583 (ti::mail-whois-parse-paragraph
3584 "^OrgName:.*[\r\n]OrgID:"
3587 ;;; ----------------------------------------------------------------------
3589 (defun ti::mail-whois-parse-registrant-domain ()
3590 "See `ti::mail-whois-parse-registrant'."
3593 ;; descr: Mr. Postman BBS
3594 ;; admin-o: ZENON-ORG-RIPN
3595 ;; nserver: dns1.zenon.net.
3596 ;; nserver: dns2.zenon.net.
3597 ;; created: 1996.10.01
3598 ;; state: Delegated till 2003.11.01
3599 ;; changed: 1998.08.11
3600 ;; mnt-by: ZENON-MNT-RIPN
3602 (ti::mail-whois-parse-paragraph
3604 "^domain:[ \t]+[a-z].*\\.[a-z0-9].+[ \t\r\n]"
3606 ;; Name: Belgacom Skynet DnsMasters
3607 ;; Company: Belgacom Skynet SA/NV
3608 "\\|^Licensee:[ \t]*$")
3611 ;;; ----------------------------------------------------------------------
3613 (defun ti::mail-whois-parse-registrant ()
3614 "Whois: Parse registrant from buffer. See `ti::mail-whois-parse'."
3615 (let ((point (point))
3617 (flet ((search (func)
3620 (dolist (func '(ti::mail-whois-parse-registrant-1
3621 ti::mail-whois-parse-registrant-domain
3622 ti::mail-whois-parse-registrant-organization
3623 ti::mail-whois-parse-registrant-organization-2))
3624 (when (setq ret (search func))
3627 ;;; ----------------------------------------------------------------------
3629 (defun ti::mail-whois-parse-tech ()
3630 "Whois: Parse tech from buffer. See `ti::mail-whois-parse'."
3631 (let ((point (point)))
3632 (or (ti::mail-whois-parse-paragraph
3633 "^[ \t]*.*Technical Contact.*:"
3634 (ti::mail-whois-parse-paragraph-end-condition))
3636 ((and (goto-char point)
3637 (re-search-forward ":\\(.*tech.*@.*\\)" nil 'noerr))
3638 (ti::mail-whois-parse-cleanup
3639 (match-string 1)))))))
3641 ;;; ----------------------------------------------------------------------
3643 (defun ti::mail-whois-parse-zone ()
3644 "Whois: Parse zone from buffer. See `ti::mail-whois-parse'."
3645 (let ((point (point)))
3646 (or (ti::mail-whois-parse-paragraph
3647 "^[ \t]*.*Zone Contact.*:"
3648 (ti::mail-whois-parse-paragraph-end-condition))
3650 ((and (goto-char point)
3651 (re-search-forward ":\\(.*zone.*@.*\\)" nil 'noerr))
3652 (ti::mail-whois-parse-cleanup
3653 (match-string 1)))))))
3655 ;;; ----------------------------------------------------------------------
3657 ;;; It the response is like this, there is no information
3658 ;;; about the created, expires
3660 ;;; # ARIN WHOIS database, last updated 2003-08-25 19:15
3661 ;;; # Enter ? for additional hints on searching ARIN's WHOIS database.
3663 (defun ti::mail-whois-parse-records ()
3664 "Whois: Parse records from buffer. See `ti::mail-whois-parse'.
3665 Values examined are: expires, created and updated."
3673 "-\\([A-Z][a-z][a-z]\\)"
3674 "-\\([0-9][0-9][0-9][0-9]\\)"
3683 "-\\([0-9][0-9]?\\)"
3684 "-\\([0-9][0-9][0-9][0-9]\\)"
3687 ;; Mon, Aug 10, 1998
3691 "[A-Z][a-z][a-z],[ \t]*"
3692 "\\([A-Z][a-z][a-z]\\)[ \t]+" ;; Mon
3693 "\\([0-9]+\\)[ \t]*,[ \t]*" ;; day
3694 "\\([0-9][0-9][0-9][0-9]\\)" ;; year
3701 "\\([0-9][0-9][0-9][0-9]\\)"
3704 "[ \t]+[0-9][0-9]:[0-9][0-9]"
3711 "\\([0-9][0-9][0-9][0-9]\\)"
3712 "[.]\\([0-9][0-9]\\)"
3713 "[.]\\([0-9][0-9]\\)"
3718 ;; changed: 20001107 15:03:09
3719 ;; changed: registdom@tin.it 20030403
3721 "\\(\\([0-9][0-9][0-9][0-9]\\)"
3732 "^[ \t]*Record[ \t]+expires[ \t]+on[ \t]+"
3733 "\\|^[ \t]*Expires[ \t]+on"
3734 "\\|^expire:[^\r\n0-9]+"
3735 "\\|^[ \t]*expiration date:[ \t]+"
3741 "^[ \t]*Record[ \t]+created[ \t]+on[ \t]+"
3742 "\\|^[ \t]*Created[ \t]+on.*[ \t]+"
3743 "\\|^created:[^\r\n0-9]+"
3744 "\\|^[ \t]*creation date:[ \t]+"
3750 "^.*last.*updated?[ \t]+on[ \t]+"
3751 "\\|^[ \t]*updated date:[ \t]+"
3752 "\\|^changed:[^\r\n0-9]+"
3756 (dolist (elt search)
3757 (multiple-value-bind (type line)
3759 (dolist (date-data date-info)
3760 (multiple-value-bind (regexp pos-list)
3762 (setq regexp (concat line regexp))
3763 ;; The order of the fields can be anything, start over
3764 ;; every time from the same point
3766 (when (re-search-forward regexp nil 'noerr)
3767 (multiple-value-bind (raw day month year)
3770 (match-string (nth 0 pos-list))
3771 (match-string (nth 1 pos-list))
3772 (match-string (nth 2 pos-list)))
3773 (if (eq 3 (length month))
3774 (setq month (ti::month-to-number
3779 (list (format "%s-%s-%s" year month day)
3785 ;;; ----------------------------------------------------------------------
3787 (defun ti::mail-whois-parse-servers ()
3788 "Whois: Parse servers from buffer. See `ti::mail-whois-parse'."
3789 (when (re-search-forward "^[ \t]*Domain servers" nil t)
3797 ;; Domain servers in listed order:
3799 ;; NS1.GALLERYHOSTING.NET 209.19.90.117
3800 ;; GHZ.DDAHL.COM 209.19.90.118
3802 (while (re-search-forward
3805 "\\([^ \t\r\n]+\\.[^ \t\r\n]+\\)"
3807 "\\([^ \t\r\n]+\\.[^ \t\r\n]+\\)")
3809 (push (list (downcase (match-string 1))
3812 ;; Domain servers in listed order:
3814 ;; Name Server: ns1.dr-parkingservices.com
3815 ;; Name Server: ns2.dr-parkingservices.com
3819 (while (re-search-forward
3821 "^[ \t]+Name[ \t]+Server:"
3823 "\\([^ \t\r\n]+\\.[^ \t\r\n]+\\)")
3825 (push (list (downcase (match-string 1)) nil)
3829 ;;; ----------------------------------------------------------------------
3831 (defun ti::mail-whois-parse-admin ()
3832 "Whois: Parse Administrative Contact from buffer.
3833 See `ti::mail-whois-parse'."
3834 (let ((point (point)))
3836 ((and (goto-char point)
3837 (re-search-forward "^[ \t]*Administrative Contact:" nil 'noerr))
3839 (let ((beg (point)))
3840 ;; Search "Technical Contact:"
3841 (when (re-search-forward "^[ \t]*.+:[ \t]*$" nil 'noerr)
3842 (ti::mail-whois-parse-cleanup
3844 beg (1- (line-beginning-position)))))))
3845 ((and (goto-char point)
3846 (re-search-forward ":\\(.*admin.*@.*\\)" nil 'noerr))
3847 (ti::mail-whois-parse-cleanup
3848 (match-string 1))))))
3850 ;;; ----------------------------------------------------------------------
3852 (defun ti::mail-whois-error-p (string)
3853 "Check if Whois call failed by examining STRING"
3856 "registra\\(nt\\|r\\):"
3859 ;; descr: Mr. Postman BBS
3860 ;; admin-o: ZENON-ORG-RIPN
3861 ;; nserver: dns1.zenon.net.
3862 ;; nserver: dns2.zenon.net.
3863 ;; created: 1996.10.01
3864 ;; state: Delegated till 2003.11.01
3865 ;; changed: 1998.08.11
3866 ;; mnt-by: ZENON-MNT-RIPN
3869 ;; domain: siemens.at
3870 ;; descr: [organization]:Siemens AG
3871 ;; descr: [street address]:Siemensstr. 92
3873 "\\|^domain:[ \t]+[a-z].*\\..*[\n\r]"
3874 "\\(type\\|descr\\):"
3875 "\\|^address:.*[^ \t\r\n]"
3877 "\\|^# ARIN WHOIS database")
3880 ;;; ----------------------------------------------------------------------
3882 (defun ti::mail-whois-parse (string)
3883 "Parse whois output STRING.
3887 '((email . ((ADDED EMAIL REST) ;; ADDED is \"REST <EMAIL>\"
3889 (registrant . STRING)
3892 (records . ((expires DATE-ISO RAW-DATE)
3893 (created DATE-ISO RAW-DATE)
3894 (updated DATE-ISO RAW-DATE))
3895 (servers . ((host ip)
3900 All the keys, like 'admin', are present in returned list, but any of the
3901 `cdr' values or their components may be nil, if no value was found.
3903 Do not relay in the order of these fields. They may change
3904 any time. Instead access the list entry with `assq'.
3908 See functions ti::mail-whois-parse-*
3909 and macro `ti::with-mail-whois'."
3912 (ti::buffer-text-properties-wipe)
3913 (let* ((referral (progn (ti::pmin)
3914 (ti::mail-whois-parse-referral)))
3915 (email (progn (ti::pmin)
3916 (ti::mail-whois-parse-email)))
3917 (registrant (progn (ti::pmin)
3918 (ti::mail-whois-parse-registrant)))
3919 (admin (progn (ti::pmin)
3920 (ti::mail-whois-parse-admin)))
3921 (tech (progn (ti::pmin)
3922 (ti::mail-whois-parse-tech)))
3923 (zone (progn (ti::pmin)
3924 (ti::mail-whois-parse-zone)))
3925 (records (progn (ti::pmin)
3926 (ti::mail-whois-parse-records)))
3927 (servers (progn (ti::pmin)
3928 (ti::mail-whois-parse-servers))))
3931 (error "TinyLibMail: Cannot parse Whois string %s" string))
3933 (cons 'referral referral)
3935 (cons 'registrant registrant)
3939 (cons 'records records)
3940 (cons 'servers servers)))))
3942 ;;; ----------------------------------------------------------------------
3944 (put 'ti::with-mail-whois 'edebug-form-spec '(body))
3945 (put 'ti::with-mail-whois 'lisp-indent-function 1)
3946 (defmacro ti::with-mail-whois (string &rest body)
3947 "For full ´whois' output STRING run BODY.
3949 The following access variables are available within BODY. Any
3950 of the values may be nil.
3953 admin Administrative Contact
3954 tech Technical Contact
3957 servers Domain servers
3961 `ti::mail-whois-parse'."
3962 `(let ((whois-data (ti::mail-whois-parse ,string)))
3964 (referral (cdr (assq 'referral whois-data)))
3965 (registrant (cdr (assq 'registrant whois-data)))
3966 (email (cdr (assq 'email whois-data)))
3967 (admin (cdr (assq 'admin whois-data)))
3968 (tech (cdr (assq 'tech whois-data)))
3969 (zone (cdr (assq 'zone whois-data)))
3970 (records (cdr (assq 'records whois-data)))
3971 (servers (cdr (assq 'servers whois-data))))
3974 ;;; ----------------------------------------------------------------------
3977 ;;; David L. Dahl (DDAHL-DOM)
3979 ;;; Chicago, IL 60657
3982 ;;; Domain Name: DDAHL.COM
3984 ;;; Administrative Contact:
3985 ;;; Dahl, David (DD4553) ddahl@DDAHL.COM
3986 ;;; 3450 N. Lakeshore Dr. #2605
3987 ;;; Chicago, IL 60657
3989 ;;; 773-934-1738 fax: 847-746-8841
3990 ;;; Technical Contact:
3991 ;;; Network Solutions, Inc.(HOST-ORG) customerservice@networksolutions.com
3992 ;;; 21355 Ridgetop Circle
3993 ;;; Dulles, VA 20166
3995 ;;; 1-888-642-9675 fax: 123 123 1234
3997 ;;; Record expires on 31-Mar-2005.
3998 ;;; Record created on 18-Sep-2002.
3999 ;;; Database last updated on 23-Aug-2003 04:47:44 EDT.
4001 ;;; Domain servers in listed order:
4003 ;;; NS1.GALLERYHOSTING.NET 209.19.90.117
4004 ;;; GHZ.DDAHL.COM 209.19.90.118
4005 ;;; WWW.CONDOSYSTEMS.COM 64.202.114.20
4008 (defun ti::mail-whois (site &optional options verb bin)
4009 "Call `whois' and return results.
4010 Web interface is at http://www.internic.net/whois.html
4014 site Top level domain. Make sure you have called
4015 ´ti::mail-ip-top-level-domain' first.
4016 OPTIONS list, additional options. E.g. -h HOST
4017 VERB flag, if non-nil print verbose messages. (Recommended)
4018 BIN Location of the binary."
4019 (let* ((path (or bin
4020 (get 'ti::mail-whois 'binary)
4021 (executable-find "whois")
4022 (error "No `whois' binary found.")))
4024 (put 'ti::mail-whois 'binary path)
4026 (not (ti::listp options)))
4027 (error "OPTIONS must be a list."))
4028 (when (string-match "\\.[0-9][0-9]?[0-9]?$\\|\\.[a-z][a-z][a-z]*$" site)
4032 (message "TinylibMail: whois %s ..." site))
4034 (apply 'call-process
4037 '(t t) ;; mix stdout and stderr
4041 (message "TinylibMail: whois %s ...done." site))
4044 ;;; ----------------------------------------------------------------------
4046 (defun ti::mail-nslookup-parse ()
4047 "Parse nslookup output in current buffer forward.
4051 Non-authoritative answer:
4052 Server: this.server.com
4053 Address: nnnn.nnn.nnn.nnn
4055 Name: NAME.ANSWER.COM
4056 Addresses: NNN.NNN.NNN.NNN,NNN.NNN.NNN.NNN
4060 '(NAME.ANSWER.COM (NNN.NNN.NNN.NNN NNN.NNN.NNN.NNN ..))."
4063 (re "[ \t]+\\([^ \t\r\n]+\\)")
4064 (name-regexp (concat "name:" re))
4065 (regexp1 (concat "address:" re))
4066 (regexp2 "addresses:[ \t]+\\([^\r\n]+\\)"))
4067 (when (re-search-forward "^[ \t]*$" nil t)
4069 (when (re-search-forward name-regexp nil t)
4070 (setq name (match-string 1))
4072 ((re-search-forward regexp1 nil t)
4073 (setq ip-list (list (match-string 1))))
4074 ((re-search-forward regexp2 nil t)
4075 (let ((ip (match-string 1)))
4077 (if (not (string-match "," ip))
4079 (list (split-string ip "[ \t,]+")))))))))
4081 (list name ip-list))))
4083 ;;; ----------------------------------------------------------------------
4085 ;;; % nslookup 204.253.213.3
4086 ;;; Name Server: example.com
4087 ;;; Address: 131.228.134.50
4089 ;;; Name: librum.sourcery.com
4090 ;;; Address: 204.253.213.3
4092 ;;; Can also have string:
4094 ;;; *** No address information is available for "mktg@inet.com"
4096 ;;; NOTE: There may be "Addresses:"
4097 ;;; =========================================================
4099 ;;; Server: ns3.tpo.fi
4100 ;;; Address: 212.63.10.250
4103 ;;; Addresses: 216.115.109.6, 216.115.109.7
4105 (defun ti::mail-nslookup (ip &optional options verb bin)
4106 "Run `nslookup' for IP.
4110 If IP address does not match 2-3 alphabetic character or max 3 digits
4111 at the end, then the address is not checked at all. It is immediately
4116 IP numeric on normal site address.
4117 OPTIONS list, additional options. E.g. -query=any
4118 VERB flag, if non-nil print verbose messages. (Recommended)
4119 BIN Location of the binary
4125 If nslookup fails, the return value is '(ORIG-IP nil)"
4126 (let* ( ;; It's faster to use absolute pathname.
4129 (get 'ti::mail-nslookup 'binary)
4130 (executable-find "nslookup")
4131 (error "No `nslookup' binary found.")))
4133 (put 'ti::mail-nslookup 'binary path)
4135 (not (ti::listp options)))
4136 (error "OPTIONS must be a list."))
4139 (message "TinylibMail: nslookup %s ..." ip))
4141 "\\.[0-9][0-9]?[0-9]?$\\|\\.[a-z][a-z][a-z]*$" ip)
4144 (apply 'call-process
4147 '(t t) ;; mix stdout and stderr
4151 (message "TinylibMail: nslookup %s ...done." ip))
4152 (unless (ti::re-search-check "No address information")
4154 (ti::mail-nslookup-parse)))))
4156 ;;; ----------------------------------------------------------------------
4158 (put 'ti::with-mail-nslookup 'edebug-form-spec '(body))
4159 (put 'ti::with-mail-nslookup 'lisp-indent-function 1)
4160 (defmacro ti::with-mail-nslookup (data &rest body)
4161 "with resault of `ti::mail-nslookup' DATA '(ip (ip ...)) run BODY.
4162 The following variables are available during looping within BODY:
4165 `(multiple-value-bind (ip-name ip-list)
4169 (dolist (ip-found ip-list)
4172 ;;; ----------------------------------------------------------------------
4174 (defun ti::mail-dig (ip &optional options verb bin)
4179 If IP address does not match 2-3 alphabetic character or max 3 digits
4180 at the end, then the address is not checked at all. It is immediately
4185 IP numeric on normal site address.
4186 OPTIONS list, additional options. E.g. -query=any
4187 VERB flag, if non-nil print verbose messages. (Recommended)
4188 BIN Location of the binary
4194 If nslookup fails, the return value is '(ORIG-IP nil)"
4195 (let* ( ;; It's faster to use absolute pathname.
4198 (get 'ti::mail-dig 'binary)
4199 (executable-find "dig")
4200 (error "No `nslookup' binary found.")))
4202 (put 'ti::mail-dig 'binary path)
4204 (not (ti::listp options)))
4205 (error "OPTIONS must be a list."))
4208 (message "TinylibMail: dig %s ..." ip))
4210 "\\.[0-9][0-9]?[0-9]?$\\|\\.[a-z][a-z][a-z]*$" ip)
4213 (apply 'call-process
4216 '(t t) ;; mix stdout and stderr
4220 (message "TinylibMail: dig %s ...done." ip))
4226 ;;; ............................................................ &misc ...
4228 ;;; ----------------------------------------------------------------------
4230 (defun ti::mail-get-buffer (&optional mode-list)
4231 "Return open mail buffer if one exists.
4232 MODE-LIST is the search order precedence. It can take values
4233 'mail-mode 'message-mode and any
4234 other valid mail like modes.
4238 ;; Return some mail-mode buffer. If there is none, then
4239 ;; return some message-mode buffer.
4241 (ti::mail-get-buffer '(mail-mode message-mode))"
4245 (setq mode-list '(mail-mode message-mode mh-letter-mode)))
4246 (dolist (buffer (buffer-list))
4247 (with-current-buffer buffer
4248 (dolist (mode mode-list)
4249 (when (eq major-mode mode)
4251 ;; We keep the separate mode in the plist
4253 ;; LIST: plist 'MODE1 --> '(buffer buffer ...)
4254 ;; : plist 'MODE2 --> '(buffer buffer ...)
4256 (setq list (get 'list mode)) ;Read current list
4257 (push (current-buffer) list) ;Add one
4259 (put 'list mode list)))))
4261 ;; Step through mode lists and return first buffer
4263 (dolist (mode mode-list)
4264 (when (setq buffer (car-safe (get 'list mode)))
4268 ;;; ----------------------------------------------------------------------
4270 (defun ti::mail-signature-insert-break (&optional point)
4271 "Insert RFC signature break to current point or POINT if no sig break exist.
4272 According to RFC there must be \"-- \\n\" before signature. The extra space
4273 separates the signature from e.g. digest messages that come with \"--\\n\"
4275 We try to find this string forward and it is not there we add one."
4277 (if point (goto-char point))
4278 (if (null (re-search-forward "^-- \n" nil t))
4281 ;;; ----------------------------------------------------------------------
4283 (defun ti::mail-yank (&optional prefix)
4284 "Yank message to current point and add optional PREFIX. GNUS/RMAIL."
4286 (yb (ti::mail-mail-buffer-name)) ;where is the yank buffer ?
4288 ;; See this mail is called from GNUS
4290 ;; - If GNUS isn't loaded, set buf name to nil
4292 (gnus-buf (and (boundp 'gnus-article-buffer)
4293 (symbol-value 'gnus-article-buffer)))
4295 ;; Test if gnus-reply; the buffers are the same
4297 (gnus-r (and gnus-buf
4298 (string= gnus-buf yb))))
4302 ;; (mail-yank-original '(4)) ; mimic C-u C-c C-y == no indent
4303 ;; - bypass all, see sendmail::mail-yank-original
4304 ;; this is more robust, and runs no extra hooks
4305 ;; - If in GNUS, the buffer will be *Article*, which is
4306 ;; narrowed to headers...widen the buffer before yanking.
4309 (progn ; normal mail
4310 (mail-yank-original '(4)))
4311 (save-excursion (set-buffer yb) (widen))
4314 (delete-blank-lines)
4316 (string-rectangle p (point-max) prefix)))))
4318 ;;; ----------------------------------------------------------------------
4320 (defun ti::mail-trim-buffer ()
4321 "Trim email message so that there are no trailing white spaces.
4322 - at the beginning of message
4323 - at the end of each line
4324 - at the end of message.
4326 If cannot find text start point, uses `point-min'. The point is not preserved.
4329 t there is no text. All white spaces were removed
4331 (let ((beg (ti::mail-text-start))
4334 (ti::buffer-replace-regexp "[ \t]+$" 0 "") ;right hand spaces (ragged lines)
4337 ;; Beginning of email message
4339 (ti::buffer-trim-blanks beg (point-max))
4340 (ti::buffer-delete-until-non-empty-line nil beg)
4342 (ti::buffer-delete-until-non-empty-line 'backward (point-max))
4345 ;; Any text left ? Signing empty file is not sensible...
4347 (if (eq (point) beg)
4349 ;; Note: User may write message "123" to the body, but we must
4350 ;; require final newline every time: "123\n", the trim
4351 ;; command will remove any exeessive newlines.
4353 (if (not (char= (preceding-char) ?\n))
4359 ;;{{{ fields, headers
4361 ;;; .......................................................... &fields ...
4363 (defsubst ti::mail-field-space-count (field-name &optional field-value )
4364 "Check how many spaces is at the beginning of field.
4367 FIELD-NAME If given, fetch FIELD-NAME like 'to' and check it's value.
4368 FIELD-VALUE If given, use this string as field content. Argument
4369 FIELD-NAME is ignored.
4375 (and (or (stringp field-name) (error "Missing field-name"))
4376 (setq field-value (ti::mail-get-field field-name)))
4379 (length (ti::string-match "^[^ ]*\\( +\\)" 1 field-value))))
4381 ;;; ----------------------------------------------------------------------
4383 (defun ti::mail-field-start (field-re &optional move max)
4384 "Return starting point of FIELD-RE or nil. Optionally MOVE to it.
4386 Supposes that field has following format, the cursor -!- position
4387 signifies returned point.
4393 FIELD-RE field regexp
4394 MOVE should we move to found point? (beginning-of-line)
4395 MAX search until MAX point"
4399 (when (re-search-forward field-re max t)
4401 (when (re-search-forward ":" max t)
4402 (setq ret (point)))))
4407 ;;; ----------------------------------------------------------------------
4409 (defun ti::mail-next-field-start (&optional move back max)
4410 "Return starting point of next field or nil. Optionally move to field.
4414 If you're somewhere else than inside header area, the return value
4420 BACK move backward (field start)
4421 MAX search until this point. PLEASE USE THIS TO LIMIT SEARCH
4427 (let ((func (if back 're-search-backward 're-search-forward))
4435 (if (and (bobp) back) ;first field
4438 ;; Next line must have text, otherwise the headers have ended
4449 (looking-at ".*[a-zA-Z0-9]"))
4450 (setq opoint (point))
4452 ;; In the last field, the previsu regexp skips too much,
4453 ;; see where the cursor (*) is. We search backward if possible
4454 ;; to find header separator (empty line)
4462 (when (and (setq point (funcall func "^[^ \t]" max t))
4463 (eq func 're-search-forward))
4464 (goto-char opoint) ;Try again
4465 (if (re-search-forward "^$" nil t)
4466 ;; no, it was further ahead, use previous search pos
4467 (if (< (point) point)
4468 (setq point (point)))))
4472 (setq ret (point))))
4474 ;; Hm, next line is empty line, not a field for us any more.
4482 ;;; ----------------------------------------------------------------------
4484 (defsubst ti::mail-field-string-wrap (string)
4485 "Wrap i.e. delete embedded newlines in string.
4493 X-My: one line two line three line."
4494 (replace-regexp-in-string "[\r\n][ \t]+" " " string))
4496 ;;; ----------------------------------------------------------------------
4498 (defsubst ti::mail-field-string-p (string)
4499 "Check if string starts with Field:
4500 Subexpression 1 contains field name and 2 contains rest."
4501 (string-match "^\\([A-Z][^:]+\\):\\(.*\\)" string))
4503 ;;; ----------------------------------------------------------------------
4505 (defun ti::mail-field-line-p ()
4506 "Return `field' name if the bginning of line contains 'NNNN:'."
4507 (let ((str (buffer-substring
4508 (line-beginning-position)
4509 (line-end-position))))
4510 (when (ti::mail-field-string-p str)
4511 (match-string 1 str))))
4513 ;;; ----------------------------------------------------------------------
4515 (defun ti::mail-field-read-line-at-point (&optional wrap)
4516 "Read whole header field at point. Field may continue in separate line.
4517 Point -!- must be at the beginning line of field.
4519 X-More: this is one line-!-
4520 That is wrapped to send
4523 If WRAP is non-nil, call `ti::mail-field-string-wrap'."
4524 (let ((beg (line-beginning-position))
4525 (end (ti::mail-next-field-start)))
4527 (let ((line (buffer-substring beg (1- end))))
4529 (ti::mail-field-string-wrap line)
4532 ;;; ----------------------------------------------------------------------
4534 (defun ti::mail-field-read-fuzzy (&optional wrap)
4535 "Read whole header field at point.
4536 The point can be anywhere in the field.
4537 If WRAP is non-nil, call `ti::mail-field-string-wrap'."
4540 (unless (ti::mail-field-line-p)
4541 (ti::mail-next-field-start 'move 'back))
4542 (ti::mail-field-read-line-at-point wrap)))
4544 ;;; ----------------------------------------------------------------------
4546 (defun ti::mail-current-field-name ()
4547 "Return name of field at current point or nil."
4549 (when (or (not (bolp))
4551 ;; Newly opened line - continuation of e.g. To: field.
4552 (looking-at "^[ \t]*$")))
4553 (ti::mail-next-field-start 'move 'back))
4554 (ti::mail-field-line-p)))
4556 ;;; ----------------------------------------------------------------------
4558 (defun ti::mail-field-email-send-p (&optional header-regexp)
4559 "Check if point is at field To, Cc or Bcc"
4560 (let ((field (ti::mail-current-field-name)))
4564 "^\\(to\\|cc\\|bcc\\)$")
4568 ;;; ----------------------------------------------------------------------
4570 (defun ti::mail-field-email-address-p ()
4571 "Check if point is at field To, Cc, Bcc, From, Sender."
4572 (ti::mail-field-email-send-p
4573 "^\\(to\\|cc\\|bcc\\|from\\|sender\\)$"))
4575 ;;; ----------------------------------------------------------------------
4577 (defun ti::mail-kill-field-in-body (list)
4578 "Kill LIST of field that are inserted into body of message."
4579 (ti::narrow-safe (ti::mail-text-start) (point-max)
4580 (dolist (header list)
4581 (ti::mail-kill-field header))))
4583 ;;; ----------------------------------------------------------------------
4585 (defun ti::mail-kill-field (field-re &optional replace-str)
4586 "Delete header field. Remember to supply Anchor '^' in FIELD-RE.
4590 FIELD-RE any regexp matching a line
4591 REPLACE-STR replace field content with this
4595 t field changed or killed
4596 nil nothing done [field not exist]"
4597 (let ((hdr-end (ti::mail-hmax))
4603 (setq replace-str (ti::string-verify-ends replace-str " " nil 'beg)))
4606 (when (and (setq beg (ti::mail-field-start field-re 'move))
4607 (setq end (ti::mail-next-field-start))
4609 ;;; (setq F field-re B beg E end)
4612 (delete-region beg end)
4613 (insert (concat replace-str "\n")))
4615 (delete-region (point) end)
4618 ;;; ----------------------------------------------------------------------
4620 (defun ti::mail-get-field-1 (field)
4621 "Read FIELD by finding regexp matching '^FIELD:'.
4622 Starting searching from the beginning of buffer. You are encouraged to call
4623 this function instead of `ti::mail-get-field' if you want to get the
4624 field information fast e.g. in `post-command-hook'.
4626 This function is not as reliable as `ti::mail-get-field', because
4627 the search is not limited to header area, but for regular headers
4628 you can use this function safely."
4629 (let ((re (format "^%s:" field))
4634 (if (and (re-search-forward re nil t)
4636 end (ti::mail-next-field-start)))
4637 (buffer-substring beg (1- end))))))
4639 ;;; ----------------------------------------------------------------------
4640 ;;; - This is almost the same as mail-utils.el/mail-fetch-field,
4641 ;;; but offers more control. It can get citated fields too, if
4642 ;;; ANY parameter is non-nil.
4643 ;;; - And it returns _strict_ content of the field, fetch-field strips
4646 (defun ti::mail-get-field (field &optional any mode)
4647 "Return field content.
4651 FIELD field name without anchor '^' and char ':'
4652 ANY return any field. When non-nil, drops anchor ^
4653 from the ^field: criteria
4654 MODE nil read the field as is, returning all chars
4656 t If field has only spaces, Return nil
4657 'pure Include header name as well as content.
4661 nil or contents of field."
4662 (let ((case-fold-search t) ;ignore case = t
4664 (concat field ":") ; pick first one met
4665 (concat "^" field ":"))) ; require STRICT HEADER
4667 (hmax (if any nil (ti::mail-text-start)))
4672 (when (and (setq beg (ti::mail-field-start re 'move hmax))
4673 (setq end (ti::mail-next-field-start nil nil hmax)))
4674 (when (and (eq mode 'pure)
4675 (looking-at "[\t ]*[^\n\t ]+")) ;not empty
4678 (setq ret (buffer-substring beg (1- end)))))
4681 (string-match "^[ \t\n\r]*\\'" ret))
4685 ;;; ----------------------------------------------------------------------
4686 ;;; - If you want simple filed adding to your mail, then have a look
4687 ;;; at this instead:
4689 ;;; (defconst my-mail-info-string "Emacs RMAIL in 19.28")
4690 ;;; (setq mail-default-headers
4692 ;;; "X-info: " my-mail-info-string "\n"))
4694 (defun ti::mail-add-field (field text &optional look-field mode replace)
4695 "Add FIELD and puts TEXT into it.
4696 If field already exist, replaces field text.
4697 By default, field is added to the end of header.
4701 FIELD string, like \"To\".
4702 TEXT \\n at end is optional. _No_ colon and _no_ spaces.
4703 LOOK-FIELD new field will be added after this. _No_ colon at end.
4704 if MODE is non-nil, field is added before this field
4706 If there is no LOOK-FIELD, nothing is done and nil
4710 REPLACE if non-nil Any previous field is removed. You probably
4711 want to set this flag to non-nil if you only want unique
4717 nil nothing done, maybe look-field doesn't exist ?"
4718 (let* ((field-re (concat "^" field ":")))
4723 (ti::mail-kill-field field-re)) ;Remove
4724 (when (and look-field
4725 (ti::mail-field-start (concat "^" look-field) 'move))
4726 (unless mode ;use only forward
4727 (ti::mail-next-field-start 'move mode))
4729 (insert (concat field ": " text))
4732 (if (mail-fetch-field field)
4733 (ti::mail-kill-field field-re text)
4734 (mail-position-on-field field)
4738 ;;; ----------------------------------------------------------------------
4740 (defun ti::mail-add-to-field-string (field string &optional look-field sep )
4741 "Find FIELD and add STRING to the. Field is created if it does not exist.
4745 FIELD string WITHOUT colon, anchor or spaces.
4747 LOOK-FIELD field name. If Field does not exist, add field after this field.
4748 See `ti::mail-add-field'
4749 SEP defaults to comma and space."
4755 (let ((content (mail-fetch-field field)))
4756 (if (ti::nil-p content)
4757 (ti::mail-add-field field string look-field)
4758 (re-search-forward (concat "^" field ":"))
4759 (ti::mail-next-field-start 'move)
4760 (skip-chars-backward " \n\t")
4761 (insert sep string)))))
4763 ;;; ----------------------------------------------------------------------
4765 (defun ti::mail-kill-field-elt (re &optional field)
4766 "Kill all elts matching RE from FIELD, which defaults to cc.
4767 Elements are supposed to be separated by commas.
4772 CC: me@example.com, you@example.com
4774 ;; If called with this
4776 (ti::mail-kill-field-elt \"me\")
4777 --> To: him@example.com
4778 --> CC: you@example.com
4780 ;; If called with this; all elts are matched and thus the
4783 (ti::mail-kill-field-elt \".\")
4784 --> To: him@example.com"
4788 (setq field (or field "CC"))
4790 (when (setq fld (ti::mail-get-field field))
4791 ;; remove spread lines
4793 (setq fld (replace-regexp-in-string "[\n\f\t ]+" "" fld))
4794 (setq fld (split-string fld "[,]+")) ; divide into items
4796 ;; ... ... ... ... ... ... ... ... ... ... ... ... remove items . .
4799 (ti::list-find fld re
4802 (not (string-match arg elt))))
4805 ;; ... ... ... ... ... ... ... ... ... ... ... . build up string . .
4809 (setq flag t ;done 1st line
4810 str (concat " " elt))
4813 (if (> (+ (length str) (length elt)) 70)
4816 ;; ... ... ... ... ... ... ... ... ... ... ... ... write new fld . .
4818 (ti::mail-kill-field (concat "^" field) str) ;replace
4819 ;; Remove whole field, all entries were discarded.
4821 (ti::mail-kill-field (concat "^" field))))))
4823 ;;; ----------------------------------------------------------------------
4824 ;;; - This is mainly for converting your mail to anon post by
4825 ;;; removing any headers you might have added.
4827 (defun ti::mail-kill-non-rfc-fields (&optional list)
4828 "Kill all non RFC fields unless LIST (HEADER-NAME-SYMBOL .. ) list is given.
4831 `ti::mail-required-headers' ,default rfc headers"
4833 (ti::mail-required-headers)
4834 (error "(ti::mail-required-headers) returned nil")))
4835 (case-fold-search t)
4838 ;; First we gather all valid headers to list
4840 (setq fld (symbol-name elt))
4841 (when (setq elt (ti::mail-get-field fld))
4842 (ti::nconc list (format "%s:%s" (capitalize fld) elt))))
4843 ;; Now we kill all headers and yank the valid ones back.
4844 (ti::mail-hmax 'move)
4845 (delete-region (point-min) (point))
4848 (insert elt "\n"))))
4850 ;;; ----------------------------------------------------------------------
4852 (defun ti::mail-get-all-email-addresses
4853 (&optional field-list abbrev-alist no-expand)
4854 "Return all email addresses from FIELD-LIST.
4858 FIELD-LIST Eg. '(\"To\" \"CC\"). Default is To CC and BCC.
4859 ABBREV-ALIST see function `ti::mail-abbrev-expand-mail-aliases'
4860 NO-EXPAND if non-nil, Do not expand addresses on current buffer.
4863 '(str str ..) notice that there may be \"\" empty strings"
4864 (let ((buffer (if no-expand
4865 (generate-new-buffer "*tmp*")
4873 (or field-list (setq field-list '("To" "CC" "BCC")))
4876 (dolist (fld field-list)
4879 (ti::mail-get-field fld nil 'pure)
4882 (with-current-buffer buffer
4883 (if str (insert str))
4885 (ti::save-with-marker-macro
4886 (ti::mail-abbrev-expand-mail-aliases
4893 ;;; (pop-to-buffer (current-buffer)) (ti::d! "MT:ABB" field-list)
4895 (dolist (elt field-list)
4896 (when (setq field (mail-fetch-field elt))
4897 (setq mems (split-string field "[,\n]+")) ;members ?
4898 ;;; (ti::d! elt field mems)
4900 (unless (ti::nil-p mem)
4901 (push mem ret))))))) ;; with-current + progn
4902 ;; make sure temp buffer is removed.
4905 (kill-buffer buffer)))
4908 ;;; ----------------------------------------------------------------------
4910 (defun ti::mail-set-recipients (to-list &optional cc-list cc-flag)
4911 "Compose current mail message to TO-LIST and add info about CC-LIST.
4915 TO-LIST List of real recipients.
4916 CC-LIST List of additional recipients that are put to
4917 X-Cc-Info. These are not actual CC members.
4918 CC-FLAG Treat CC-LIST as actual recipients. This is like combining
4919 TO-LIST and CC-LIST. No X-Cc-Info field is added."
4921 (setq to-list (ti::list-merge-elements to-list cc-list)
4924 (ti::mail-kill-field "^To")
4925 (ti::mail-kill-field "^CC")
4926 (ti::mail-kill-field "^X-Cc-Info")
4928 (ti::mail-add-field "To" (pop to-list))
4930 (ti::mail-add-field "Cc" (mapconcat 'concat to-list ",")))
4935 (concat "Additional recipient(s)\n "
4936 (mapconcat 'concat cc-list ",")))))
4939 ;;{{{ News, articles
4941 ;;; ............................................................ &News ...
4943 ;;; ----------------------------------------------------------------------
4945 (defun ti::mail-news-buffer-p ()
4946 "Check if current buffer is news post, followup or the like."
4949 ((and (eq major-mode 'message-mode) (fboundp 'message-news-p))
4950 (ti::funcall 'message-news-p))
4951 ((and (eq major-mode 'message-mode) (boundp 'message-this-is-news))
4952 (symbol-value 'message-this-is-news))
4953 ((string-match "news" (symbol-name major-mode)))
4955 ;; Gnus keeps it in 'message-mode', so search this header then
4958 (re-search-forward "Newsgroups\\|References:\\|Gcc:" nil t)))))
4960 ;;; ----------------------------------------------------------------------
4962 (defun ti::mail-article-regexp-read-line (re &optional level)
4963 "Switch to article buffer; match RE at LEVEL and return match."
4966 (setq level (or level 0)))
4967 (ti::mail-with-article-buffer
4969 (if (re-search-forward re nil t)
4970 (setq line (match-string level))))
4973 ;;; ----------------------------------------------------------------------
4974 ;;; - This is useful if you use same hook for both
4975 ;;; regular mail posting AND for gnuis posting.
4976 ;;; - It makes it possible to decicede inside hook, which post
4977 ;;; type this is. Eg. setting extra headers for NEWS and not
4978 ;;; different for regular Mail
4980 (defun ti::mail-news-reply-p ()
4981 "Return type of message being composed.
4982 This function is meaningful _only_ when you use it inside
4983 some GNUS or mail hook. The buffer must be current mail buffer.
4988 (let* ((mode (symbol-name major-mode))
4990 ;; GNUS might not be loaded in this emacs
4991 (gnus-buf (if (boundp 'gnus-article-buffer)
4992 ;; normally name is "Article"
4993 (symbol-value 'gnus-article-buffer)
4996 (mail-buf (ti::mail-mail-buffer-name)) ;YANK buffer name?
4998 ;; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ GNUS
4999 ;; - Detect news posting mode. The mail program uses
5000 ;; YANK from the gnus buffer "*Article*" So we can detect
5001 ;; if this is gnus post
5002 ;; - gnus 'news-mail-reply-->rnewspost.el
5004 (gnus (string= gnus-buf mail-buf))
5007 (string-match "news" mode)
5008 (save-excursion ;Gnus
5010 (re-search-forward "^References:" nil t)))))
5015 ;;{{{ anon.penet.fi anon-nymserver.com
5017 ;;; ............................................................ &anon ...
5019 ;;; ----------------------------------------------------------------------
5021 (defun ti::mail-anon-penet-p (email)
5022 "Check if EMAIL is penet anon address."
5023 (string-match "[an][an][0-9]+@.*penet.fi" email))
5025 ;;; ----------------------------------------------------------------------
5027 (defun ti::mail-anon-penet-to-p ()
5028 "Check if the TO: field contain anon.penet.fi address.
5032 email if it's anon address."
5033 (let ((to (ti::mail-get-field "to")))
5034 (if (and to (ti::mail-anon-penet-p to))
5037 ;;; ----------------------------------------------------------------------
5039 (defun ti::mail-nymserver-email-convert (email &optional na-mode)
5040 "Convert penet EMAIL address.
5042 If NA-MODE is nil: do 'an' conversion
5043 anXXX@example.com --> anXXX
5044 naXXX@example.com --> anXXX
5045 VANITY@example.com --> VANITY@example.com
5046 VANITY.an@example.com --> VANITY@example.com
5047 VANITY.na@example.com --> VANITY@example.com
5049 If NA-MODE is non-nil:
5050 Then do opposite 'na' conversion"
5053 (if (string-match "\\(an\\)[0-9]+@\\|\\.\\(an\\)@" email)
5054 (setq email (ti::replace-match 1 "na" email))
5055 ;; the email is VANITY@example.com
5056 (if (string-match "\\(.*\\)\\(@.*\\)" email)
5058 (match-string 1 email) ".na"
5059 (match-string 2 email))))))
5062 ((string-match "\\(na\\)[0-9]+@" email)
5063 (setq email (ti::replace-match 1 "an" email)))
5064 ((string-match "\\(\\.na\\)@" email)
5065 (setq email (ti::replace-match 1 "" email))))))
5071 ;;; ............................................................ &mime ...
5073 ;;; ----------------------------------------------------------------------
5075 (defsubst ti::mail-mime-tm-featurep-p ()
5076 "TM. Check if MIME is loaded."
5077 (and (featurep 'mime-setup)
5078 (not (featurep 'semi-setup))))
5080 ;;; ----------------------------------------------------------------------
5082 (defsubst ti::mail-mime-semi-featurep-p ()
5083 "SEMI. Check if MIME is loaded."
5084 (featurep 'semi-setup))
5086 ;;; ----------------------------------------------------------------------
5088 (defsubst ti::mail-mime-feature-p ()
5089 "MIME. Check if TM/ or SEMI is available."
5090 (or (ti::mail-mime-tm-featurep-p)
5091 (ti::mail-mime-semi-featurep-p)))
5093 ;;; ----------------------------------------------------------------------
5095 (defsubst ti::mail-mime-tm-edit-p ()
5096 "TM. Check if mime edit is active."
5097 (and (boundp 'mime/editor-mode-flag)
5098 (symbol-value 'mime/editor-mode-flag)))
5100 ;;; ----------------------------------------------------------------------
5102 (defsubst ti::mail-mime-semi-edit-p ()
5103 "SEMI. Check if mime edit is active."
5104 (and (boundp 'mime-edit-mode-flag)
5105 (symbol-value 'mime-edit-mode-flag)))
5107 ;;; ----------------------------------------------------------------------
5109 (put 'ti::mail-mime-tm-edit-mode-macro 'lisp-indent-function 0)
5110 (put 'ti::mail-mime-tm-edit-mode-macro 'edebug-form-spec '(body))
5111 (defmacro ti::mail-mime-tm-edit-mode-macro (&rest body)
5112 "TM. Run body If mime edit mode is active in current buffer."
5114 (when (and (ti::mail-mime-tm-featurep-p) (ti::mail-mime-tm-edit-p))
5117 ;;; ----------------------------------------------------------------------
5119 (put 'ti::mail-mime-semi-edit-mode-macro 'lisp-indent-function 0)
5120 (put 'ti::mail-mime-semi-edit-mode-macro 'edebug-form-spec '(body))
5121 (defmacro ti::mail-mime-semi-edit-mode-macro (&rest body)
5122 "SEMI. Run body If mime edit mode is active in current buffer."
5124 (when (and (ti::mail-mime-semi-featurep-p) (ti::mail-mime-semi-edit-p))
5127 ;;; ----------------------------------------------------------------------
5129 (put 'ti::mail-mime-funcall-0-macro 'lisp-indent-function 1)
5130 (put 'ti::mail-mime-funcall-0-macro 'edebug-form-spec '(body))
5131 (defmacro ti::mail-mime-funcall-0-macro (func-tm func-semi)
5132 "Call function FUNC-TM or FUNC-SEMI with no arguments."
5135 ((and (ti::mail-mime-tm-featurep-p) (ti::mail-mime-tm-edit-p))
5136 (ti::funcall (, func-tm))
5138 ((and (ti::mail-mime-semi-featurep-p) (ti::mail-mime-semi-edit-p))
5139 (ti::funcall (, func-semi))))))
5141 ;;; ----------------------------------------------------------------------
5143 (put 'ti::mail-mime-funcall-2-macro 'lisp-indent-function 3)
5144 (put 'ti::mail-mime-funcall-2-macro 'edebug-form-spec '(body))
5145 (defmacro ti::mail-mime-funcall-2-macro (func-tm func-semi arg1 arg2)
5146 "Call function FUNC-TM or FUNC-SEMI with ARG1 ARG2."
5149 ((and (ti::mail-mime-tm-featurep-p) (ti::mail-mime-tm-edit-p))
5150 (ti::funcall (, func-tm) (, arg1) (, arg2))
5152 ((and (ti::mail-mime-semi-featurep-p) (ti::mail-mime-semi-edit-p))
5153 (ti::funcall (, func-semi) (, arg1) (, arg2))
5156 ;;; ----------------------------------------------------------------------
5158 (defun ti::mail-mime-turn-on-mode ()
5159 "Turn on MIME mode. Do nothing if mime is not available.
5160 Return t if mime was supported."
5163 ((ti::mail-mime-tm-featurep-p)
5164 (unless (ti::mail-mime-tm-edit-p)
5165 (ti::funcall 'mime/editor-mode))
5167 ((ti::mail-mime-semi-featurep-p)
5168 (unless (ti::mail-mime-semi-edit-p)
5169 (ti::funcall 'mime-edit-mode))
5172 ;;; ----------------------------------------------------------------------
5174 (defun ti::mail-mime-turn-off-mode ()
5175 "Turn off MIME mode. Do nothing if mime is not available.
5176 Return t if mime was supported."
5179 ((ti::mail-mime-tm-featurep-p)
5180 (when (ti::mail-mime-tm-edit-p)
5181 (ti::funcall 'mime-editor/exit))
5183 ((ti::mail-mime-semi-featurep-p)
5184 (when (ti::mail-mime-semi-edit-p)
5185 (ti::funcall 'mime-edit-exit))
5188 ;;; ----------------------------------------------------------------------
5190 (defun ti::mail-mime-sign-region (&optional beg end)
5191 "MIME. Enclose region BEG END as signed.
5194 BEG Defaults to mail beginning or buffer beginning.
5195 END Defualts to `point-max'
5199 nil if mime is not available.
5202 (ti::mail-set-region beg end)
5203 (ti::mail-mime-funcall-2-macro
5204 'mime-editor/enclose-signed-region
5205 'mime-edit-enclose-pgp-signed-region
5209 ;;; ----------------------------------------------------------------------
5211 (defun ti::mail-mime-encrypt-region (&optional beg end)
5212 "MIME. Enclose region BEG END as encrypted
5215 BEG Defaults to mail beginning or buffer beginning.
5216 END Defualts to `point-max'
5220 nil if mime is not available.
5223 (ti::mail-set-region beg end)
5224 (ti::mail-mime-funcall-2-macro
5225 'mime-editor/enclose-encrypted-region
5226 'mime-edit-enclose-pgp-encrypted-region
5230 ;;; ----------------------------------------------------------------------
5232 (put 'ti::mail-mime-tm-split-macro 'lisp-indent-function 0)
5233 (put 'ti::mail-mime-tm-split-macro 'edebug-form-spec '(body))
5234 (defmacro ti::mail-mime-tm-split-macro (&rest body)
5235 "TM. Define variables `split' `max' `parts' and run BODY if TM active.
5236 You have to use variables `max' and `parts' otherwise you don't need this macro."
5238 (when (boundp 'mime-editor/split-message)
5239 (let* ((split (symbol-value 'mime-editor/split-message))
5240 (max (symbol-value 'mime-editor/message-default-max-lines))
5241 (lines (count-lines (point-min) (point-max)))
5242 (parts (1+ (/ lines max))))
5244 (setq split nil)) ; No-op Bytecomp silencer
5247 ;;; ----------------------------------------------------------------------
5249 (defun ti::mail-mime-maybe-p ()
5250 "Check if buffer possibly contain MIME sections.
5251 if there is boundary string in header or if the TM -mime tags
5252 '-[[' are found from buffer, then it's considered mime."
5253 (or (ti::mail-mime-p)
5256 ((featurep 'tm-edit) ;TM.el
5257 ;; TM puth these markes to MIME section; Try to find one.
5258 ;; This can be only found if the mimi-edit mode is not
5259 ;; yet exited. Upon exit the message will match true
5260 ;; MIME (ti::mail-mime-p).
5261 (ti::re-search-check (concat "^" (regexp-quote "--[["))))))))
5263 ;;; ----------------------------------------------------------------------
5265 (defun ti::mail-mime-p ()
5266 "Check if buffer has mime message. You probably want `ti::mail-mime-maybe-p'.
5267 It must contain boundary field in the headers and the boundary
5268 must be found from the message body itself.
5269 Only the header is not enough to say it's a composed mime mail.
5272 boundary=\"Multipart_Thu_Sep_19_12:46:36_1996-1\"
5275 This text must be found after the headers until the MIME criteria is
5278 (let ((field (ti::mail-get-field "Content-Type" 'any))
5281 ;; Content-Type field may not include "boundary"
5282 ;; --> it's not multipart mime.
5283 (setq re (ti::string-match ".*boundary=.\\(.*\\)\"" 1 field)))
5284 (setq re (regexp-quote re))
5285 ;; start finding the boundary text after the headers.
5287 (ti::pmin) (re-search-forward re) ;This is the header, ignore it
5289 (re-search-forward re nil t)))))
5291 ;;; ----------------------------------------------------------------------
5292 ;;; #todo: not tested
5294 (defun ti::mail-mime-qp-decode(from to)
5295 "Mime. Decode quoted-printable from region between FROM and TO."
5298 (while (search-forward "=" to t)
5299 (cond ((char= (following-char) ?\n)
5302 ((looking-at "[0-9A-F][0-9A-F]")
5304 (insert (hexl-hex-string-to-integer
5305 (buffer-substring (point) (+ 2 (point)))))
5307 ((message "Malformed MIME quoted-printable message"))))))
5309 ;;; ----------------------------------------------------------------------
5310 ;;; (add-hook 'vm-select-message-hook 'ti::mail-mime-prepare-qp)
5312 (defun ti::mail-qp-mime-prepare ()
5313 "Mime. Unquote quoted-printable from mail buffers.
5316 content-transfer-encoding: quoted-printable"
5319 (let ((case-fold-search t)
5320 (type (mail-fetch-field "content-transfer-encoding"))
5323 ((and (stringp type)
5324 (string-match "quoted-printable" type))
5327 (search-forward "\n\n" nil 'move)
5328 (message "MIME Unquoting printable...")
5329 (ti::mail-mime-qp-decode (point) (point-max))
5330 (message "MIME Unquoting printable...done"))))))
5336 ;;; .................................................... &mail-sending ...
5338 ;;; ----------------------------------------------------------------------
5340 (defun ti::mail-plugged-p ()
5341 "Check if computer is on-line. This function relies on Gnus."
5342 (when (boundp 'gnus-plugged)
5343 (symbol-value 'gnus-plugged)))
5345 ;;; ----------------------------------------------------------------------
5347 (defun ti::mail-sendmail-reset-send-hooks ()
5348 "Make `mail-send-hook' et al. buffer local and set to nil."
5349 (dolist (sym '(mail-send-hook
5351 mh-before-send-letter-hook))
5353 (make-local-hook sym)
5356 ;;; ----------------------------------------------------------------------
5358 (put 'ti::mail-sendmail-pure-env-macro 'lisp-indent-function 0)
5359 (put 'ti::mail-sendmail-pure-env-macro 'edebug-form-spec '(body))
5360 (defmacro ti::mail-sendmail-pure-env-macro (&rest body)
5361 "Reset all mail/message hooks/vars locally to nil and run BODY."
5363 (let* (message-setup-hook
5367 mail-archive-file-name
5368 mail-default-headers
5369 mail-default-reply-to)
5370 ;; byteComp silencer: "Not used variables."
5371 (if mail-mode-hook (setq mail-mode-hook nil))
5372 (if mail-setup-hook (setq mail-setup-hook nil))
5373 (if mail-archive-file-name (setq mail-archive-file-name nil))
5374 (if mail-default-headers (setq mail-default-headers nil))
5375 (if mail-default-reply-to (setq mail-default-reply-to nil))
5378 ;;; ----------------------------------------------------------------------
5380 (put 'ti::mail-sendmail-macro-1 'lisp-indent-function 3)
5381 (put 'ti::mail-sendmail-macro-1 'edebug-form-spec '(body))
5382 (defmacro ti::mail-sendmail-macro-1 (to subject send &rest body)
5383 "See `ti::mail-sendmail-macro' instead. This is low level function."
5386 (ti::mail-sendmail-pure-env-macro
5387 ;; to subject in-reply-to cc replybuffer actions
5389 (mail-setup (, to) (, subject) nil nil nil nil)
5391 (ti::mail-kill-field "^fcc")
5392 (ti::mail-text-start 'move)
5395 (ti::kill-buffer-safe " sendmail temp") ;See sendmail-send-it
5397 (mail-send-and-exit nil))))))
5399 ;;; ----------------------------------------------------------------------
5401 (put 'ti::mail-sendmail-macro 'lisp-indent-function 3)
5402 (put 'ti::mail-sendmail-macro 'edebug-form-spec '(body))
5403 (defmacro ti::mail-sendmail-macro (to subject send &rest body)
5404 "Send / construct mail according to parameters.
5405 Use TO, SUBJECT and If SEND if non-nil, send mail after BODY finishes.
5407 Point is at the beginning of body.
5411 `mail-mode-hook' `mail-setup-hook' `mail-archive-file-name'
5412 `mail-default-headers'
5414 are set to nil. If you need these, please copy them before calling this
5415 macro and restore their values in BODY, possibly calling
5416 and using them as sendmail normally would.
5418 The hooks are set to nil so that mail buffer is created fast and
5419 that nothing causes trouble when mail buffer is ready."
5421 (let* ((BuffeR (ti::temp-buffer ti:mail-mail-buffer 'clear)))
5422 (save-window-excursion
5423 (with-current-buffer BuffeR
5424 (ti::mail-sendmail-macro-1
5432 ;;{{{ Abbrevs: XEmacs and Emacs
5434 ;;; ......................................................... &abbrevs ...
5436 ;;; ----------------------------------------------------------------------
5438 (defun ti::mail-abbrev-table ()
5439 "XEmacs and Emacs Compatibility, Return mail abbrev hash table."
5440 (ti::package-require-mail-abbrevs)
5445 (ti::funcall 'mail-abbrevs-setup))
5449 (build-mail-aliases)
5453 ;; in Emacs this is a list, in XEmacs this is a HASH
5457 (build-mail-aliases)
5459 ;; See mail-abbrev.el
5460 (when (get-buffer "mailrc")
5461 (pop-to-buffer (get-buffer "mailrc")))
5465 ;;; ----------------------------------------------------------------------
5467 (defun ti::mail-abbrev-expand-mail-aliases (beg end &optional alias-alist)
5468 "Expand aliases in region BEG END.
5469 Please Cache results from `ti::mail-abbrev-get-alist' and
5470 use the result as argument ALIAS-ALIST. Otherwise aliases are always
5471 reuild from scratch."
5478 ((and (require 'mailalias nil 'noerr) ;Emacs Feature only
5479 (fboundp 'expand-mail-aliases))
5480 (ti::funcall 'expand-mail-aliases beg end))
5482 (t ;Too bad, this is much slower
5484 (setq alias-alist (ti::mail-abbrev-get-alist)))
5487 (narrow-to-region beg end) (ti::pmin)
5488 (while (re-search-forward
5489 "^[ \t]+\\|^[ \t]*[\n,][ \t]*\\|:[ \t]*" nil t)
5490 (when (setq word (ti::buffer-match"[^ \t,\n]+" 0))
5492 (setq mb (match-beginning 0)
5495 ;; Do not count field names, like "CC:" words
5496 (when (and (not (string-match ":$" word))
5498 (setq exp (assoc word alias-alist)))
5499 (setq exp (cdr exp)) ; Change alias to expansion
5500 (delete-region mb me)
5503 ;; This isn't very smart formatting, the layout
5504 ;; is so that each expansion is on it's own line,
5505 ;; no fancy lining up things -- Mail me back
5506 ;; with diff to this code if you code nicer one.
5508 (when (looking-at "[ \t]*,") ;put on separate lines
5509 (goto-char (match-end 0))
5510 (when (not (looking-at "[ \t]*$"))
5512 (beginning-of-line)))))))))))
5514 ;;; ----------------------------------------------------------------------
5515 ;;; See mailabbrev.el how to build your abbrevs.
5517 (defun ti::mail-abbrev-get-alist (&optional expand-until)
5518 "Return alist of all `mail-abbrevs'.
5519 Build the abbrev table from your ~/.mailrc with command
5520 \\[build-mail-abbrevs]. The following parameter is _not_ yet functional.
5523 EXPAND-UNTIL expand until the RH elt is pure email.
5526 '((ABBREV-STRING . EXPANDED-STRING) (A . E) ..)"
5528 (pre-abbrev-expand-hook nil) ;; prevent recursion
5529 (mail-abbrev-aliases-need-to-be-resolved t)
5534 ;; XEmacs 19.14 no-op for ByteCompiler
5536 (unless mail-abbrev-aliases-need-to-be-resolved
5537 (setq mail-abbrev-aliases-need-to-be-resolved nil))
5539 (setq table (ti::mail-abbrev-table))
5542 ((listp table) ;; mail-aliases is already in (A . S) form
5543 (setq exp-list table))
5545 ;; We have to expand abbrevs by hand because XEmacs doesn't
5546 ;; parse them like emacs mail-alias
5549 (let ((tmp (generate-new-buffer "*ti::mail-abbrev*")))
5550 (with-current-buffer tmp
5551 (setq local-abbrev-table table)
5556 (setq elt (prin1-to-string (identity x)))
5557 (when (not (string= "0" elt)) ;abbrev in this slot?
5562 ;; BBDB does some voodoo with the abbrevs by
5563 ;; setting the function cell, and sometimes calling
5564 ;; expand-abbrev by BBDB blessed abbrev gives error.
5565 ;; --> Don't bother with the error, since the
5566 ;; abbrevs is correctly expanded, but BBDB cries about
5567 ;; "wrong marker" or something.
5574 "tinylibmail: `expand-abbrev' signalled ERROR `%s'"
5575 " while expanding `%s'")
5576 (prin1-to-string err)
5578 (push (cons (symbol-name x) (ti::read-current-line)) exp-list)
5582 (kill-buffer tmp))))) ;; cond
5585 ;;; ----------------------------------------------------------------------
5587 (defun ti::mail-mail-abbrevs-email-list (&optional abbrev-alist)
5588 "Build email list of abbrevs; optionally use ABBREV-ALIST.
5589 Only entries in this format in ~/.mailrc are returned. There must be
5590 no \",\" chained lists in the line.
5592 alias[spaces]some[spaces]user@address.xx
5596 '((\"abbrev\" . \"expansion\") (A . E) ..)
5605 (or abbrev-alist (ti::mail-abbrev-get-alist)))
5606 (setq str (cdr elt))
5607 (when (null (string-match "," str)) ;filter out multiple mail lists
5608 (setq email (ti::string-match "\\([^< \t]+@[^> \t\n]+\\)" 0 str))
5618 (provide 'tinylibmail)
5619 (run-hooks 'ti:mail-load-hook)
5623 ;;; tinylibmail.el ends here