Remove the Scheme implementation of the Sieve language.
* NEWS: Describe the change. * doc/texinfo/mailutils.texi: Remove description of sieve2scm * doc/texinfo/programs.texi: Likewise. * scheme/Makefile.am: Remove sieve2scm. * scheme/mimeheader.scm: Remove. * scheme/numaddr.scm: Remove. * scheme/redirect.scm: Remove. * scheme/reject.scm: Remove. * scheme/sieve-core.scm: Remove. * scheme/sieve2scm.scmi: Remove. * scheme/vacation.scm: Remove.
Showing
11 changed files
with
14 additions
and
2168 deletions
1 | GNU mailutils NEWS -- history of user-visible changes. 2017-04-08 | 1 | GNU mailutils NEWS -- history of user-visible changes. 2017-04-09 |
2 | Copyright (C) 2002-2017 Free Software Foundation, Inc. | 2 | Copyright (C) 2002-2017 Free Software Foundation, Inc. |
3 | See the end of file for copying conditions. | 3 | See the end of file for copying conditions. |
4 | 4 | ||
... | @@ -88,6 +88,15 @@ defined. Instead, the following constants are defined in config.h: | ... | @@ -88,6 +88,15 @@ defined. Instead, the following constants are defined in config.h: |
88 | 88 | ||
89 | * movemail: new option --progress-meter | 89 | * movemail: new option --progress-meter |
90 | 90 | ||
91 | * scheme implementation of the Sieve language discontinued | ||
92 | |||
93 | There's no reason to keep two different implementations of the Sieve | ||
94 | language within the same package. The principal implementation | ||
95 | (libmu_sieve) is faster, much more advanced and rich in features than | ||
96 | the implementation in Scheme. The decision has therefore been taken to | ||
97 | discontinue the latter and to concentrate all efforts on the further | ||
98 | development of the former. | ||
99 | |||
91 | 100 | ||
92 | Version 3.2 - 2017-03-11 | 101 | Version 3.2 - 2017-03-11 |
93 | 102 | ... | ... |
... | @@ -257,7 +257,6 @@ Reading Mail | ... | @@ -257,7 +257,6 @@ Reading Mail |
257 | @command{sieve} | 257 | @command{sieve} |
258 | 258 | ||
259 | * sieve interpreter:: A Sieve Interpreter | 259 | * sieve interpreter:: A Sieve Interpreter |
260 | * sieve2scm:: A Sieve to Scheme Translator and Filter | ||
261 | 260 | ||
262 | A Sieve Interpreter | 261 | A Sieve Interpreter |
263 | 262 | ... | ... |
... | @@ -5902,14 +5902,11 @@ only the first. | ... | @@ -5902,14 +5902,11 @@ only the first. |
5902 | @UNREVISED | 5902 | @UNREVISED |
5903 | 5903 | ||
5904 | Sieve is a language for filtering e-mail messages at time of final | 5904 | Sieve is a language for filtering e-mail messages at time of final |
5905 | delivery, described in RFC 3028. GNU Mailutils provides two | 5905 | delivery, described in RFC 3028. GNU Mailutils contains |
5906 | implementations of this language: a stand-alone @dfn{sieve interpreter} | 5906 | stand-alone @dfn{sieve interpreter}, which is described in detail below. |
5907 | and a @dfn{sieve translator and filter}. The following sections describe these | ||
5908 | utilities in detail. | ||
5909 | 5907 | ||
5910 | @menu | 5908 | @menu |
5911 | * sieve interpreter:: A Sieve Interpreter | 5909 | * sieve interpreter:: A Sieve Interpreter |
5912 | * sieve2scm:: A Sieve to Scheme Translator and Filter | ||
5913 | @end menu | 5910 | @end menu |
5914 | 5911 | ||
5915 | @node sieve interpreter | 5912 | @node sieve interpreter |
... | @@ -6250,41 +6247,6 @@ source for the required action NAME is not available | ... | @@ -6250,41 +6247,6 @@ source for the required action NAME is not available |
6250 | @end enumerate | 6247 | @end enumerate |
6251 | 6248 | ||
6252 | @c *********************************************************************** | 6249 | @c *********************************************************************** |
6253 | |||
6254 | @page | ||
6255 | @node sieve2scm | ||
6256 | @subsection A Sieve to Scheme Translator and Filter | ||
6257 | @UNREVISED | ||
6258 | |||
6259 | A Sieve to Scheme Translator @command{sieve2scm} translates a given | ||
6260 | Sieve script into an equivalent Scheme program and optionally executes | ||
6261 | it. The program itself is written in Scheme and requires presence of | ||
6262 | Guile version 1.8 or newer on the system. For more information on | ||
6263 | Guile refer to @ref{Top,,Overview,guile,The Guile Reference Manual}. | ||
6264 | |||
6265 | @table @option | ||
6266 | @item -f @var{filename} | ||
6267 | @itemx --file @var{filename} | ||
6268 | Set input file name. | ||
6269 | |||
6270 | @item -o @var{filename} | ||
6271 | @itemx --output @var{filename} | ||
6272 | Set output file name | ||
6273 | |||
6274 | @item -L @var{dirname} | ||
6275 | @itemx --lib-dir @var{dirname} | ||
6276 | Set sieve library directory name | ||
6277 | |||
6278 | @item -d @var{level} | ||
6279 | @itemx --debug @var{level} | ||
6280 | Set debugging level | ||
6281 | @end table | ||
6282 | |||
6283 | The Scheme programs produced by @command{sieve2scm} can be used with | ||
6284 | @command{guimb} or @command{maidag}. | ||
6285 | |||
6286 | @c *********************************************************************** | ||
6287 | |||
6288 | @page | 6250 | @page |
6289 | @node guimb | 6251 | @node guimb |
6290 | @section @command{guimb} --- A Mailbox Scanning and Processing Language | 6252 | @section @command{guimb} --- A Mailbox Scanning and Processing Language | ... | ... |
... | @@ -15,19 +15,7 @@ | ... | @@ -15,19 +15,7 @@ |
15 | ## You should have received a copy of the GNU General Public License | 15 | ## You should have received a copy of the GNU General Public License |
16 | ## along with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. | 16 | ## along with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. |
17 | 17 | ||
18 | bin_SCRIPTS = sieve2scm guimb | 18 | bin_SCRIPTS = guimb |
19 | |||
20 | # FIXME: Sieve2scm is temporarly exempted from installchecks because | ||
21 | # it may fail starting during checks, if libguile-mailutils-v- library | ||
22 | # has not been previously installed. The proper fix would be to alter | ||
23 | # %load-path during tests. | ||
24 | AM_INSTALLCHECK_STD_OPTIONS_EXEMPT=sieve2scm | ||
25 | |||
26 | sievemoddir=@MU_GUILE_SIEVE_MOD_DIR@ | ||
27 | |||
28 | sieve2scm: sieve2scm.scmi package.sed | ||
29 | $(AM_V_GEN)sed -f package.sed $(srcdir)/sieve2scm.scmi > sieve2scm | ||
30 | $(AM_V_at)chmod +w sieve2scm | ||
31 | 19 | ||
32 | guimb: guimb.scmi package.sed | 20 | guimb: guimb.scmi package.sed |
33 | $(AM_V_GEN)sed -f package.sed $(srcdir)/guimb.scmi > guimb | 21 | $(AM_V_GEN)sed -f package.sed $(srcdir)/guimb.scmi > guimb |
... | @@ -37,26 +25,12 @@ package.sed: Makefile | ... | @@ -37,26 +25,12 @@ package.sed: Makefile |
37 | $(AM_V_GEN)echo 's,%GUILE_BINDIR%,@GUILE_BINDIR@,g' > package.sed | 25 | $(AM_V_GEN)echo 's,%GUILE_BINDIR%,@GUILE_BINDIR@,g' > package.sed |
38 | $(AM_V_at)echo 's,%BINDIR%,$(bindir),g' >> package.sed | 26 | $(AM_V_at)echo 's,%BINDIR%,$(bindir),g' >> package.sed |
39 | $(AM_V_at)echo 's,%GUILE_SITE%,$(GUILE_SITE),g' >> package.sed | 27 | $(AM_V_at)echo 's,%GUILE_SITE%,$(GUILE_SITE),g' >> package.sed |
40 | $(AM_V_at)echo 's,%LIBDIR%,$(sievemoddir),g' >> package.sed | ||
41 | $(AM_V_at)echo 's,%PACKAGE%,$(PACKAGE),g' >> package.sed | 28 | $(AM_V_at)echo 's,%PACKAGE%,$(PACKAGE),g' >> package.sed |
42 | $(AM_V_at)echo 's,%VERSION%,$(VERSION),g' >> package.sed | 29 | $(AM_V_at)echo 's,%VERSION%,$(VERSION),g' >> package.sed |
43 | 30 | ||
44 | CLEANFILES = sieve2scm guimb package.sed | 31 | CLEANFILES = guimb package.sed |
45 | |||
46 | sitedir=@GUILE_SITE@/$(PACKAGE) | ||
47 | site_DATA=sieve-core.scm | ||
48 | |||
49 | sievemod_DATA=\ | ||
50 | mimeheader.scm\ | ||
51 | numaddr.scm\ | ||
52 | redirect.scm\ | ||
53 | reject.scm\ | ||
54 | vacation.scm | ||
55 | 32 | ||
56 | EXTRA_DIST=\ | 33 | EXTRA_DIST=\ |
57 | $(sievemod_DATA)\ | ||
58 | sieve-core.scm\ | ||
59 | sieve2scm.scmi\ | ||
60 | guimb.scmi | 34 | guimb.scmi |
61 | 35 | ||
62 | installcheck-binSCRIPTS: $(bin_SCRIPTS) | 36 | installcheck-binSCRIPTS: $(bin_SCRIPTS) | ... | ... |
scheme/mimeheader.scm
deleted
100644 → 0
1 | ;;;; GNU Mailutils -- a suite of utilities for electronic mail | ||
2 | ;;;; Copyright (C) 1999-2001, 2007, 2010-2012, 2014-2017 Free Software | ||
3 | ;;;; Foundation, Inc. | ||
4 | ;;;; | ||
5 | ;;;; GNU Mailutils is free software; you can redistribute it and/or modify | ||
6 | ;;;; it under the terms of the GNU General Public License as published by | ||
7 | ;;;; the Free Software Foundation; either version 3, or (at your option) | ||
8 | ;;;; any later version. | ||
9 | ;;;; | ||
10 | ;;;; GNU Mailutils is distributed in the hope that it will be useful, | ||
11 | ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
13 | ;;;; GNU General Public License for more details. | ||
14 | ;;;; | ||
15 | ;;;; You should have received a copy of the GNU General Public License along | ||
16 | ;;;; with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. | ||
17 | |||
18 | ;;;; This module provides GNU extension test "mimeheader". | ||
19 | |||
20 | ;;;; Syntax: mimeheader [COMPARATOR] [MATCH-TYPE] | ||
21 | ;;;; <header-names: string-list> <key-list: string-list> | ||
22 | ;;;; | ||
23 | ;;;; The "mimeheader" test evaluates to true if in any part of the | ||
24 | ;;;; multipart MIME message a header name from <header-names> list | ||
25 | ;;;; matches any key from <key-list>. If the message is not multipart, | ||
26 | ;;;; "mimeheader" test is equivalent to "header" test. | ||
27 | ;;;; | ||
28 | ;;;; The arguments to "mimeheader" test are the same as to "header" test. | ||
29 | |||
30 | ;;;; Example: | ||
31 | ;;;; | ||
32 | ;;;; require [ "mimeheader", "reject"]; | ||
33 | ;;;; if mimeheader :matches "Content-Type" "*application/msword;*" { | ||
34 | ;;;; reject "Please do not send data in a proprietary format."; | ||
35 | ;;;; } | ||
36 | |||
37 | (define (test-mimeheader header-list key-list . opt-args) | ||
38 | (if (mu-message-multipart? sieve-current-message) | ||
39 | (let ((mime-count (mu-message-get-num-parts sieve-current-message)) | ||
40 | (comp (find-comp opt-args)) | ||
41 | (match (find-match opt-args))) | ||
42 | (call-with-current-continuation | ||
43 | (lambda (exit) | ||
44 | (do ((n 1 (1+ n))) | ||
45 | ((> n mime-count) #f) | ||
46 | (let ((msg (mu-message-get-part sieve-current-message n))) | ||
47 | (if msg | ||
48 | (for-each | ||
49 | (lambda (key) | ||
50 | (let ((header-fields (mu-message-get-header-fields | ||
51 | msg | ||
52 | header-list)) | ||
53 | (rx (if (eq? match #:matches) | ||
54 | (make-regexp (sieve-regexp-to-posix key) | ||
55 | (if (eq? comp string-ci=?) | ||
56 | regexp/icase | ||
57 | '())) | ||
58 | #f))) | ||
59 | (for-each | ||
60 | (lambda (h) | ||
61 | (let ((hdr (cdr h))) | ||
62 | (if hdr | ||
63 | (case match | ||
64 | ((#:is) | ||
65 | (if (comp hdr key) | ||
66 | (exit #t))) | ||
67 | ((#:contains) | ||
68 | (if (sieve-str-str hdr key comp) | ||
69 | (exit #t))) | ||
70 | ((#:matches) | ||
71 | (if (regexp-exec rx hdr) | ||
72 | (exit #t))))))) | ||
73 | header-fields))) | ||
74 | key-list) | ||
75 | #f)))))) | ||
76 | (apply test-header header-list key-list opt-args))) | ||
77 | |||
78 | ;;; Register the test at compile time | ||
79 | (if sieve-parser | ||
80 | (sieve-register-test "mimeheader" | ||
81 | test-mimeheader | ||
82 | (list 'string-list 'string-list) | ||
83 | (append comparator match-type))) |
scheme/numaddr.scm
deleted
100644 → 0
1 | ;;;; GNU Mailutils -- a suite of utilities for electronic mail | ||
2 | ;;;; Copyright (C) 1999-2001, 2007, 2010-2012, 2014-2017 Free Software | ||
3 | ;;;; Foundation, Inc. | ||
4 | ;;;; | ||
5 | ;;;; GNU Mailutils is free software; you can redistribute it and/or modify | ||
6 | ;;;; it under the terms of the GNU General Public License as published by | ||
7 | ;;;; the Free Software Foundation; either version 3, or (at your option) | ||
8 | ;;;; any later version. | ||
9 | ;;;; | ||
10 | ;;;; GNU Mailutils is distributed in the hope that it will be useful, | ||
11 | ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
13 | ;;;; GNU General Public License for more details. | ||
14 | ;;;; | ||
15 | ;;;; You should have received a copy of the GNU General Public License along | ||
16 | ;;;; with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. | ||
17 | |||
18 | ;;;; This module provides GNU extension test "numaddr". | ||
19 | |||
20 | ;;;; Syntax: numaddr [":over" / ":under"] <header-names: string-list> | ||
21 | ;;;; <limit: number> | ||
22 | ;;;; The "numaddr" test counts Internet addresses in structured headers | ||
23 | ;;;; that contain addresses. It returns true if the total number of | ||
24 | ;;;; addresses satisfies the requested relation: | ||
25 | ;;;; | ||
26 | ;;;; If the argument is ":over" and the number of addresses is greater than | ||
27 | ;;;; the number provided, the test is true; otherwise, it is false. | ||
28 | ;;;; | ||
29 | ;;;; If the argument is ":under" and the number of addresses is less than | ||
30 | ;;;; the number provided, the test is true; otherwise, it is false. | ||
31 | ;;;; | ||
32 | ;;;; If the argument is empty, ":over" is assumed. | ||
33 | |||
34 | ;;;; Example: | ||
35 | ;;;; | ||
36 | ;;;; require [ "numaddr" ]; | ||
37 | ;;;; if numaddr :over [ "To", "Cc" ] 50 { discard; } | ||
38 | |||
39 | (define (test-numaddr header-list count . comp) | ||
40 | (let ((total-count 0) | ||
41 | (header-fields (mu-message-get-header-fields | ||
42 | sieve-current-message | ||
43 | header-list)) | ||
44 | (compfun (cond | ||
45 | ((or (null? (car comp)) (eq? (car comp) #:over)) | ||
46 | (lambda (val lim) | ||
47 | (> val lim))) | ||
48 | ((eq? (car comp) #:under) | ||
49 | (lambda (val lim) | ||
50 | (< val lim))) | ||
51 | (else | ||
52 | (runtime-message SIEVE-ERROR | ||
53 | "test-numaddr: unknown comparator " | ||
54 | comp))))) | ||
55 | (call-with-current-continuation | ||
56 | (lambda (exit) | ||
57 | (for-each | ||
58 | (lambda (h) | ||
59 | (let ((hdr (cdr h))) | ||
60 | (if hdr | ||
61 | (let ((naddr (mu-address-get-count hdr))) | ||
62 | (set! total-count (+ total-count naddr)) | ||
63 | (if (compfun total-count count) | ||
64 | (exit #t)))))) | ||
65 | header-fields) | ||
66 | #f)))) | ||
67 | |||
68 | ;;; Register the test at compile time | ||
69 | (if sieve-parser | ||
70 | (sieve-register-test "numaddr" | ||
71 | test-numaddr | ||
72 | (list 'string-list 'number) | ||
73 | size-comp)) |
scheme/redirect.scm
deleted
100644 → 0
1 | ;;;; GNU Mailutils -- a suite of utilities for electronic mail | ||
2 | ;;;; Copyright (C) 1999-2001, 2006-2007, 2010-2012, 2014-2017 Free | ||
3 | ;;;; Software Foundation, Inc. | ||
4 | ;;;; | ||
5 | ;;;; GNU Mailutils is free software; you can redistribute it and/or modify | ||
6 | ;;;; it under the terms of the GNU General Public License as published by | ||
7 | ;;;; the Free Software Foundation; either version 3, or (at your option) | ||
8 | ;;;; any later version. | ||
9 | ;;;; | ||
10 | ;;;; GNU Mailutils is distributed in the hope that it will be useful, | ||
11 | ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
13 | ;;;; GNU General Public License for more details. | ||
14 | ;;;; | ||
15 | ;;;; You should have received a copy of the GNU General Public License along | ||
16 | ;;;; with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. | ||
17 | |||
18 | ;;;; This module provides sieve's "redirect" action. | ||
19 | |||
20 | ;;; rfc3028 says: | ||
21 | ;;; "Implementations SHOULD take measures to implement loop control," | ||
22 | ;;; We do this by appending an "X-Sender" header to each message | ||
23 | ;;; being redirected. If one of the "X-Sender" headers of the message | ||
24 | ;;; contains our email address, we assume it is a loop and bail out. | ||
25 | |||
26 | (define (sent-from-me? msg) | ||
27 | (call-with-current-continuation | ||
28 | (lambda (exit) | ||
29 | (for-each | ||
30 | (lambda (hdr) | ||
31 | (if (and (string-ci=? (car hdr) "X-Sender") | ||
32 | (string-ci=? (mu-address-get-email (cdr hdr)) | ||
33 | sieve-my-email)) | ||
34 | (exit #t))) | ||
35 | (mu-message-get-header-fields sieve-current-message)) | ||
36 | #f))) | ||
37 | |||
38 | ;;; redirect action | ||
39 | (define (action-redirect address) | ||
40 | (sieve-verbose-print "REDIRECT" "to address " address) | ||
41 | (handle-exception | ||
42 | (if sieve-my-email | ||
43 | (cond | ||
44 | ((sent-from-me? sieve-current-message) | ||
45 | (runtime-message SIEVE-WARNING "Redirection loop detected")) | ||
46 | (else | ||
47 | (let ((out-msg (mu-message-copy sieve-current-message)) | ||
48 | (sender (mu-message-get-sender sieve-current-message))) | ||
49 | (mu-message-set-header out-msg "X-Sender" sieve-my-email) | ||
50 | (mu-message-send out-msg #f sender address) | ||
51 | (mu-message-destroy out-msg)) | ||
52 | (mu-message-delete sieve-current-message)))))) | ||
53 | |||
54 | ;;; Register action | ||
55 | (if sieve-parser | ||
56 | (sieve-register-action "redirect" action-redirect (list 'string) '())) | ||
57 | |||
58 | |||
59 |
scheme/reject.scm
deleted
100644 → 0
1 | ;;;; GNU Mailutils -- a suite of utilities for electronic mail | ||
2 | ;;;; Copyright (C) 1999-2001, 2006-2007, 2010-2012, 2014-2017 Free | ||
3 | ;;;; Software Foundation, Inc. | ||
4 | ;;;; | ||
5 | ;;;; GNU Mailutils is free software; you can redistribute it and/or modify | ||
6 | ;;;; it under the terms of the GNU General Public License as published by | ||
7 | ;;;; the Free Software Foundation; either version 3, or (at your option) | ||
8 | ;;;; any later version. | ||
9 | ;;;; | ||
10 | ;;;; GNU Mailutils is distributed in the hope that it will be useful, | ||
11 | ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
13 | ;;;; GNU General Public License for more details. | ||
14 | ;;;; | ||
15 | ;;;; You should have received a copy of the GNU General Public License along | ||
16 | ;;;; with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. | ||
17 | |||
18 | ;;;; This module provides sieve's "reject" action. | ||
19 | |||
20 | (define sieve-option-quote #t) | ||
21 | |||
22 | (define (action-reject reason) | ||
23 | (sieve-verbose-print "REJECT") | ||
24 | (handle-exception | ||
25 | (let ((mime (mu-mime-create 0)) | ||
26 | (datestr (strftime "%a, %b %d %H:%M:%S %Y %Z" | ||
27 | (localtime (current-time)))) | ||
28 | (sender (mu-message-get-sender sieve-current-message))) | ||
29 | (let* ((mesg (mu-message-create)) | ||
30 | (port (mu-message-get-port mesg "w"))) | ||
31 | |||
32 | (display "The original message was received at " port) | ||
33 | (display datestr port) | ||
34 | (newline port) | ||
35 | (display "from " port) | ||
36 | (display sender port) | ||
37 | (display ".\n" port) | ||
38 | |||
39 | (display "Message was refused by recipient's mail filtering program.\n" | ||
40 | port) | ||
41 | (display "Reason given was as follows:\n" port) | ||
42 | (newline port) | ||
43 | (display reason port) | ||
44 | |||
45 | (close-output-port port) | ||
46 | (mu-mime-add-part mime mesg)) | ||
47 | |||
48 | ;; message/delivery-status | ||
49 | (let* ((mesg (mu-message-create)) | ||
50 | (port (mu-message-get-port mesg "w"))) | ||
51 | (mu-message-set-header mesg "Content-Type" "message/delivery-status") | ||
52 | |||
53 | (display (string-append "Reporting-UA: sieve; " | ||
54 | mu-package-string "\n") port) | ||
55 | (display (string-append "Arrival-Date: " datestr "\n") port) | ||
56 | (newline port) | ||
57 | |||
58 | (display (string-append "Final-Recipient: RFC822; " sieve-my-email "\n") | ||
59 | port) | ||
60 | |||
61 | (display "Action: deleted\n" port); | ||
62 | (display "Disposition: automatic-action/MDN-sent-automatically;deleted\n" | ||
63 | port) | ||
64 | (display (string-append | ||
65 | "Last-Attempt-Date: " datestr "\n") port) | ||
66 | |||
67 | (close-output-port port) | ||
68 | (mu-mime-add-part mime mesg)) | ||
69 | |||
70 | ;; Quote original message | ||
71 | (let* ((mesg (mu-message-create)) | ||
72 | (port (mu-message-get-port mesg "w")) | ||
73 | (in-port (mu-message-get-port sieve-current-message "r" #t))) | ||
74 | (mu-message-set-header mesg "Content-Type" "message/rfc822") | ||
75 | |||
76 | (do ((line (read-line in-port) (read-line in-port))) | ||
77 | ((eof-object? line) #f) | ||
78 | (display line port) | ||
79 | (newline port)) | ||
80 | |||
81 | (close-input-port in-port) | ||
82 | (close-output-port port) | ||
83 | (mu-mime-add-part mime mesg)) | ||
84 | |||
85 | (mu-message-send (mu-mime-get-message mime) #f sieve-daemon-email sender) | ||
86 | (mu-message-delete sieve-current-message)))) | ||
87 | |||
88 | ;;; Register action | ||
89 | (if sieve-parser | ||
90 | (sieve-register-action "reject" action-reject (list 'string) '())) | ||
91 | |||
92 | |||
93 | |||
94 | |||
95 |
scheme/sieve-core.scm
deleted
100644 → 0
1 | ;;;; GNU Mailutils -- a suite of utilities for electronic mail | ||
2 | ;;;; Copyright (C) 1999-2001, 2006-2007, 2010-2012, 2014-2017 Free | ||
3 | ;;;; Software Foundation, Inc. | ||
4 | ;;;; | ||
5 | ;;;; GNU Mailutils is free software; you can redistribute it and/or modify | ||
6 | ;;;; it under the terms of the GNU General Public License as published by | ||
7 | ;;;; the Free Software Foundation; either version 3, or (at your option) | ||
8 | ;;;; any later version. | ||
9 | ;;;; | ||
10 | ;;;; GNU Mailutils is distributed in the hope that it will be useful, | ||
11 | ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
13 | ;;;; GNU General Public License for more details. | ||
14 | ;;;; | ||
15 | ;;;; You should have received a copy of the GNU General Public License along | ||
16 | ;;;; with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. | ||
17 | |||
18 | ;;;; This module provides core functionality for the sieve scripts. | ||
19 | |||
20 | (define-module (mailutils sieve-core)) | ||
21 | |||
22 | (use-modules (mailutils mailutils)) | ||
23 | |||
24 | ;;; Set to #t when parsing | ||
25 | (define-public sieve-parser #f) | ||
26 | |||
27 | ;;; Name of the input source | ||
28 | (define-public sieve-source "UNKNOWN") | ||
29 | |||
30 | ;;; The email address for originator of error messages. Should be <> | ||
31 | ;;; but current mailutils API is unable to parse and handle it. | ||
32 | ;;; Site administrators are supposed to replace it with the | ||
33 | ;;; actual value. | ||
34 | (define-public sieve-daemon-email "MAILER-DAEMON@localhost") | ||
35 | |||
36 | ;;; The email address of the user whose mailbox is being processed. | ||
37 | ;;; If #f, it will be set by sieve-main | ||
38 | (define-public sieve-my-email #f) | ||
39 | |||
40 | (define SIEVE-WARNING "Warning") | ||
41 | (define SIEVE-ERROR "Error") | ||
42 | (define SIEVE-NOTICE "Notice") | ||
43 | |||
44 | (defmacro handle-exception (. expr) | ||
45 | `(catch 'mailutils-error | ||
46 | (lambda () ,@expr) | ||
47 | (lambda (key . args) | ||
48 | (runtime-message SIEVE-ERROR | ||
49 | "In function " (car args) ": " | ||
50 | (apply format #f | ||
51 | (list-ref args 1) (list-ref args 2)) | ||
52 | (let ((error-code | ||
53 | (car (list-ref args (1- (length args)))))) | ||
54 | (if (= error-code 0) | ||
55 | "" | ||
56 | (string-append | ||
57 | "; Error code: " | ||
58 | (number->string error-code) | ||
59 | " - " | ||
60 | (mu-strerror error-code)))))))) | ||
61 | |||
62 | ;;; Set to #t if verbose action listing is requested | ||
63 | (define-public sieve-verbose #f) | ||
64 | |||
65 | (defmacro sieve-verbose-print (action . rest) | ||
66 | `(if sieve-verbose | ||
67 | (let ((uid (false-if-exception | ||
68 | (mu-message-get-uid sieve-current-message)))) | ||
69 | (display ,action) | ||
70 | (display " on msg uid ") | ||
71 | (display uid) | ||
72 | (let ((args (list ,@rest))) | ||
73 | (cond ((not (null? args)) | ||
74 | (display ": ") | ||
75 | (for-each | ||
76 | display | ||
77 | args)))) | ||
78 | (newline)))) | ||
79 | |||
80 | ;;; List of open mailboxes. | ||
81 | ;;; Each entry is: (list MAILBOX-NAME OPEN-FLAGS MBOX) | ||
82 | (define sieve-mailbox-list '()) | ||
83 | |||
84 | ;;; Cached mailbox open: Lookup in the list first, if not found, | ||
85 | ;;; call mu-mailbox-open and append to the list. | ||
86 | ;;; NOTE: second element of each slot (OPEN-FLAGS) is not currently | ||
87 | ;;; used, sinse all the mailboxes are open with "cw". | ||
88 | (define (sieve-mailbox-open name flags) | ||
89 | (let ((slot (assoc name sieve-mailbox-list))) | ||
90 | (if slot | ||
91 | (list-ref slot 2) | ||
92 | (let ((mbox (false-if-exception (mu-mailbox-open name flags)))) | ||
93 | (if mbox | ||
94 | (set! sieve-mailbox-list (append | ||
95 | sieve-mailbox-list | ||
96 | (list | ||
97 | (list name flags mbox))))) | ||
98 | mbox)))) | ||
99 | |||
100 | ;;; Close all open mailboxes. | ||
101 | (define (sieve-close-mailboxes) | ||
102 | (for-each | ||
103 | (lambda (slot) | ||
104 | (cond | ||
105 | ((list-ref slot 2) | ||
106 | => (lambda (mbox) | ||
107 | (mu-mailbox-close mbox))))) | ||
108 | sieve-mailbox-list) | ||
109 | (set! sieve-mailbox-list '())) | ||
110 | |||
111 | (define (sieve-expand-filename filename) | ||
112 | (case (string-ref filename 0) | ||
113 | ((#\/ #\% #\~ #\+ #\=) | ||
114 | filename) | ||
115 | (else | ||
116 | (let ((pw (getpwuid (geteuid)))) | ||
117 | (if (vector? pw) | ||
118 | (string-append (vector-ref pw 5) | ||
119 | "/" | ||
120 | filename) | ||
121 | filename))))) | ||
122 | |||
123 | ;;; Comparators | ||
124 | (define-public sieve-standard-comparators | ||
125 | (list (list "i;octet" string=?) | ||
126 | (list "i;ascii-casemap" string-ci=?))) | ||
127 | |||
128 | ;;; Stop statement | ||
129 | |||
130 | (define-public (sieve-stop) | ||
131 | (sieve-verbose-print "STOP") | ||
132 | (throw 'sieve-stop)) | ||
133 | |||
134 | ;;; Basic five actions: | ||
135 | |||
136 | ;;; reject is defined in reject.scm | ||
137 | |||
138 | ;;; fileinto | ||
139 | |||
140 | (define-public (action-fileinto filename) | ||
141 | (let ((name (sieve-expand-filename filename))) | ||
142 | (sieve-verbose-print "FILEINTO" "delivering into " name) | ||
143 | (if (string? name) | ||
144 | (let ((outbox (sieve-mailbox-open name "cw"))) | ||
145 | (cond | ||
146 | (outbox | ||
147 | (handle-exception | ||
148 | (mu-mailbox-append-message outbox sieve-current-message) | ||
149 | (mu-message-delete sieve-current-message))) | ||
150 | (else | ||
151 | (runtime-message SIEVE-ERROR | ||
152 | "Could not open mailbox " name)))) | ||
153 | (runtime-message SIEVE-ERROR | ||
154 | "Could not expand mailbox name " filename)))) | ||
155 | |||
156 | ;;; redirect is defined in redirect.scm | ||
157 | |||
158 | ;;; keep -- does nothing worth mentioning :^) | ||
159 | |||
160 | (define-public (action-keep) | ||
161 | (sieve-verbose-print "KEEP") | ||
162 | (handle-exception | ||
163 | (mu-message-delete sieve-current-message #f))) | ||
164 | |||
165 | ;;; discard | ||
166 | |||
167 | (define-public (action-discard) | ||
168 | (sieve-verbose-print "DISCARD" "marking as deleted") | ||
169 | (handle-exception | ||
170 | (mu-message-delete sieve-current-message))) | ||
171 | |||
172 | ;;; Register standard actions | ||
173 | (define-public sieve-standard-actions | ||
174 | (list (list "keep" action-keep '() '()) | ||
175 | (list "discard" action-discard '() '()) | ||
176 | (list "fileinto" action-fileinto (list 'string) '()))) | ||
177 | |||
178 | ;;; Some utilities. | ||
179 | |||
180 | (define (sieve-get-opt-arg opt-args tag default) | ||
181 | (cond | ||
182 | ((member tag opt-args) => | ||
183 | (lambda (x) | ||
184 | (car (cdr x)))) | ||
185 | (else | ||
186 | default))) | ||
187 | |||
188 | (define (find-comp opt-args) | ||
189 | (sieve-get-opt-arg opt-args #:comparator string-ci=?)) | ||
190 | |||
191 | (define (find-match opt-args) | ||
192 | (cond | ||
193 | ((member #:is opt-args) | ||
194 | #:is) | ||
195 | ((member #:contains opt-args) | ||
196 | #:contains) | ||
197 | ((member #:matches opt-args) | ||
198 | #:matches) | ||
199 | ((member #:regex opt-args) | ||
200 | #:regex) | ||
201 | (else | ||
202 | #:is))) | ||
203 | |||
204 | (define (sieve-str-str str key comp) | ||
205 | (if (string-null? key) | ||
206 | ;; rfc3028: | ||
207 | ;; If a header listed in the header-names argument exists, it contains | ||
208 | ;; the null key (""). However, if the named header is not present, it | ||
209 | ;; does not contain the null key. | ||
210 | ;; This function gets called only if the header was present. So: | ||
211 | #t | ||
212 | (let* ((char (string-ref key 0)) | ||
213 | (str-len (string-length str)) | ||
214 | (key-len (string-length key)) | ||
215 | (limit (- str-len key-len))) | ||
216 | (if (< limit 0) | ||
217 | #f | ||
218 | (call-with-current-continuation | ||
219 | (lambda (xx) | ||
220 | (do ((index 0 (1+ index))) | ||
221 | ((cond | ||
222 | ((> index limit) | ||
223 | #t) | ||
224 | ;; FIXME: This is very inefficient, but I have to use this | ||
225 | ;; provided (string-index str (string-ref key 0)) may not | ||
226 | ;; work... | ||
227 | ((comp (substring str index (+ index key-len)) | ||
228 | key) | ||
229 | (xx #t)) | ||
230 | (else | ||
231 | #f)) #f)) | ||
232 | #f)))))) | ||
233 | |||
234 | ;;; Convert sieve-style regexps to POSIX: | ||
235 | |||
236 | (define (sieve-regexp-to-posix regexp) | ||
237 | (let ((length (string-length regexp))) | ||
238 | (do ((cl '()) | ||
239 | (escape #f) | ||
240 | (i 0 (1+ i))) | ||
241 | ((= i length) (list->string (reverse cl))) | ||
242 | (let ((ch (string-ref regexp i))) | ||
243 | (cond | ||
244 | (escape | ||
245 | (set! cl (append (list ch) cl)) | ||
246 | (set! escape #f)) | ||
247 | ((char=? ch #\\) | ||
248 | (set! escape #t)) | ||
249 | ((char=? ch #\?) | ||
250 | (set! cl (append (list #\.) cl))) | ||
251 | ((char=? ch #\*) | ||
252 | (set! cl (append (list #\* #\.) cl))) | ||
253 | ((member ch (list #\. #\$ #\^ #\[ #\])) | ||
254 | (set! cl (append (list ch #\\) cl))) | ||
255 | (else | ||
256 | (set! cl (append (list ch) cl)))))))) | ||
257 | |||
258 | |||
259 | (define (get-regex match key comp) | ||
260 | (case match | ||
261 | ((#:matches) | ||
262 | (make-regexp (sieve-regexp-to-posix key) | ||
263 | (if (eq? comp string-ci=?) | ||
264 | regexp/icase | ||
265 | '()))) | ||
266 | ((#:regex) | ||
267 | (make-regexp key | ||
268 | (if (eq? comp string-ci=?) | ||
269 | regexp/icase | ||
270 | '()))) | ||
271 | (else | ||
272 | #f))) | ||
273 | |||
274 | ;;;; Standard tests: | ||
275 | |||
276 | (define-public (test-address header-list key-list . opt-args) | ||
277 | (let ((comp (find-comp opt-args)) | ||
278 | (match (find-match opt-args)) | ||
279 | (part (cond | ||
280 | ((member #:localpart opt-args) | ||
281 | #:localpart) | ||
282 | ((member #:domain opt-args) | ||
283 | #:domain) | ||
284 | (else | ||
285 | #:all)))) | ||
286 | (call-with-current-continuation | ||
287 | (lambda (exit) | ||
288 | (for-each | ||
289 | (lambda (key) | ||
290 | (let ((header-fields (mu-message-get-header-fields | ||
291 | sieve-current-message | ||
292 | header-list)) | ||
293 | (rx (get-regex match key comp))) | ||
294 | (for-each | ||
295 | (lambda (h) | ||
296 | (let ((hdr (cdr h))) | ||
297 | (if hdr | ||
298 | (let ((naddr (mu-address-get-count hdr))) | ||
299 | (do ((n 1 (1+ n))) | ||
300 | ((> n naddr) #f) | ||
301 | (let ((addr (case part | ||
302 | ((#:all) | ||
303 | (mu-address-get-email hdr n)) | ||
304 | ((#:localpart) | ||
305 | (mu-address-get-local hdr n)) | ||
306 | ((#:domain) | ||
307 | (mu-address-get-domain hdr n))))) | ||
308 | (if addr | ||
309 | (case match | ||
310 | ((#:is) | ||
311 | (if (comp addr key) | ||
312 | (exit #t))) | ||
313 | ((#:contains) | ||
314 | (if (sieve-str-str addr key comp) | ||
315 | (exit #t))) | ||
316 | ((#:matches #:regex) | ||
317 | (if (regexp-exec rx addr) | ||
318 | (exit #t)))) | ||
319 | (runtime-message SIEVE-NOTICE | ||
320 | "Can't get address parts for message " | ||
321 | sieve-current-message)))))))) | ||
322 | header-fields))) | ||
323 | key-list) | ||
324 | #f)))) | ||
325 | |||
326 | (define-public (test-size key-size . comp) | ||
327 | (let ((size (mu-message-get-size sieve-current-message))) | ||
328 | (cond | ||
329 | ((null? comp) ;; An extension. | ||
330 | (= size key-size)) | ||
331 | ((eq? (car comp) #:over) | ||
332 | (> size key-size)) | ||
333 | ((eq? (car comp) #:under) | ||
334 | (< size key-size)) | ||
335 | (else | ||
336 | (runtime-message SIEVE-ERROR "test-size: unknown comparator " comp))))) | ||
337 | |||
338 | (define-public (test-envelope part-list key-list . opt-args) | ||
339 | (let ((comp (find-comp opt-args)) | ||
340 | (match (find-match opt-args))) | ||
341 | (call-with-current-continuation | ||
342 | (lambda (exit) | ||
343 | (for-each | ||
344 | (lambda (part) | ||
345 | (cond | ||
346 | ((string-ci=? part "From") | ||
347 | (let ((sender (mu-message-get-sender sieve-current-message))) | ||
348 | (for-each | ||
349 | (lambda (key) | ||
350 | (if (comp key sender) | ||
351 | (exit #t))) | ||
352 | key-list))) | ||
353 | (else | ||
354 | (runtime-message SIEVE-ERROR | ||
355 | "Envelope part " part " not supported") | ||
356 | #f))) | ||
357 | part-list) | ||
358 | #f)))) | ||
359 | |||
360 | (define-public (test-exists header-list) | ||
361 | (call-with-current-continuation | ||
362 | (lambda (exit) | ||
363 | (for-each (lambda (hdr) | ||
364 | (let ((val (mu-message-get-header sieve-current-message hdr))) | ||
365 | (if (or (not val) (= (string-length val) 0)) | ||
366 | (exit #f)))) | ||
367 | header-list) | ||
368 | #t))) | ||
369 | |||
370 | (define-public (test-header header-list key-list . opt-args) | ||
371 | (let ((comp (find-comp opt-args)) | ||
372 | (match (find-match opt-args))) | ||
373 | (call-with-current-continuation | ||
374 | (lambda (exit) | ||
375 | (for-each | ||
376 | (lambda (key) | ||
377 | (let ((header-fields (mu-message-get-header-fields | ||
378 | sieve-current-message | ||
379 | header-list)) | ||
380 | (rx (get-regex match key comp))) | ||
381 | (for-each | ||
382 | (lambda (h) | ||
383 | (let ((hdr (cdr h))) | ||
384 | (if hdr | ||
385 | (case match | ||
386 | ((#:is) | ||
387 | (if (comp hdr key) | ||
388 | (exit #t))) | ||
389 | ((#:contains) | ||
390 | (if (sieve-str-str hdr key comp) | ||
391 | (exit #t))) | ||
392 | ((#:matches #:regex) | ||
393 | (if (regexp-exec rx hdr) | ||
394 | (exit #t))))))) | ||
395 | header-fields))) | ||
396 | key-list) | ||
397 | #f)))) | ||
398 | |||
399 | ;;; Register tests: | ||
400 | (define address-part (list (cons "localpart" #f) | ||
401 | (cons "domain" #f) | ||
402 | (cons "all" #f))) | ||
403 | (define match-type (list (cons "is" #f) | ||
404 | (cons "contains" #f) | ||
405 | (cons "matches" #f) | ||
406 | (cons "regex" #f))) | ||
407 | (define size-comp (list (cons "under" #f) | ||
408 | (cons "over" #f))) | ||
409 | (define comparator (list (cons "comparator" 'string))) | ||
410 | |||
411 | (define-public sieve-standard-tests | ||
412 | (list | ||
413 | (list "address" | ||
414 | test-address | ||
415 | (list 'string-list 'string-list) | ||
416 | (append address-part comparator match-type)) | ||
417 | (list "size" | ||
418 | test-size | ||
419 | (list 'number) | ||
420 | size-comp) | ||
421 | (list "envelope" | ||
422 | test-envelope | ||
423 | (list 'string-list 'string-list) | ||
424 | (append comparator address-part match-type)) | ||
425 | (list "exists" | ||
426 | test-exists | ||
427 | (list 'string-list) | ||
428 | '()) | ||
429 | (list "header" | ||
430 | test-header | ||
431 | (list 'string-list 'string-list) | ||
432 | (append comparator match-type)) | ||
433 | (list "false" #f '() '()) | ||
434 | (list "true" #t '() '()))) | ||
435 | |||
436 | ;;; runtime-message | ||
437 | |||
438 | (define-public (runtime-message level . text) | ||
439 | (let ((msg (apply string-append | ||
440 | (map (lambda (x) | ||
441 | (format #f "~A" x)) | ||
442 | (append | ||
443 | (list "(in " sieve-source ") ") | ||
444 | text))))) | ||
445 | (if sieve-current-message | ||
446 | (mu-message-set-header sieve-current-message | ||
447 | (string-append "X-Sieve-" level) | ||
448 | msg)) | ||
449 | (if (isatty? (current-error-port)) | ||
450 | (display (string-append level ": " msg "\n") (current-error-port))))) | ||
451 | |||
452 | ;;; Sieve-main | ||
453 | (define-public sieve-mailbox #f) | ||
454 | (define-public sieve-current-message #f) | ||
455 | |||
456 | (define-public (sieve-run-current-message thunk) | ||
457 | (and (catch 'sieve-stop | ||
458 | thunk | ||
459 | (lambda args | ||
460 | #f)) | ||
461 | (sieve-verbose-print "IMPLICIT KEEP"))) | ||
462 | |||
463 | (define (sieve-run thunk) | ||
464 | (if (not sieve-my-email) | ||
465 | (set! sieve-my-email (mu-username->email))) | ||
466 | ; (DEBUG 1 "Mailbox: " sieve-mailbox) | ||
467 | |||
468 | (let msg-loop ((msg (mu-mailbox-first-message sieve-mailbox))) | ||
469 | (if (not (eof-object? msg)) | ||
470 | (begin | ||
471 | (set! sieve-current-message msg) | ||
472 | (sieve-run-current-message thunk) | ||
473 | (msg-loop (mu-mailbox-next-message sieve-mailbox))))) | ||
474 | |||
475 | (sieve-close-mailboxes)) | ||
476 | |||
477 | (define (sieve-command-line) | ||
478 | (catch #t | ||
479 | (lambda () | ||
480 | (let ((args sieve-script-args)) | ||
481 | (append (list "<temp-file>") args))) | ||
482 | (lambda args (command-line)))) | ||
483 | |||
484 | (define-public (sieve-main thunk) | ||
485 | (handle-exception | ||
486 | (let* ((cl (sieve-command-line)) | ||
487 | (name (if (and (not (null? (cdr cl))) | ||
488 | (string? (cadr cl))) | ||
489 | (cadr cl) | ||
490 | (mu-user-mailbox-url | ||
491 | (passwd:name (mu-getpwuid (getuid))))))) | ||
492 | |||
493 | (set! sieve-mailbox (mu-mailbox-open name "rw")) | ||
494 | (sieve-run thunk) | ||
495 | (mu-mailbox-expunge sieve-mailbox) | ||
496 | (mu-mailbox-close sieve-mailbox)))) |
scheme/sieve2scm.scmi
deleted
100644 → 0
1 | #! %GUILE_BINDIR%/guile -s | ||
2 | # Emacs, it's -*- scheme -*- | ||
3 | !# | ||
4 | ;;;; GNU Mailutils -- a suite of utilities for electronic mail | ||
5 | ;;;; Copyright (C) 1999-2001, 2006-2007, 2009-2012, 2014-2017 Free | ||
6 | ;;;; Software Foundation, Inc. | ||
7 | ;;;; | ||
8 | ;;;; GNU Mailutils is free software; you can redistribute it and/or modify | ||
9 | ;;;; it under the terms of the GNU General Public License as published by | ||
10 | ;;;; the Free Software Foundation; either version 3, or (at your option) | ||
11 | ;;;; any later version. | ||
12 | ;;;; | ||
13 | ;;;; GNU Mailutils is distributed in the hope that it will be useful, | ||
14 | ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
15 | ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
16 | ;;;; GNU General Public License for more details. | ||
17 | ;;;; | ||
18 | ;;;; You should have received a copy of the GNU General Public License along | ||
19 | ;;;; with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. | ||
20 | ;;;; | ||
21 | |||
22 | ;;;; This is a Sieve to Scheme translator. | ||
23 | ;;;; | ||
24 | ;;;; To convert a sieve script into equivalent Scheme program, run: | ||
25 | ;;;; | ||
26 | ;;;; guile -s sieve2scm.scm --file <sieve-script-name> --output <output-file-name> | ||
27 | ;;;; | ||
28 | ;;;; To compile and execute a sieve script upon a mailbox, run: | ||
29 | ;;;; | ||
30 | ;;;; guile -s sieve2scm.scm --file <sieve-script-name> [mailbox-name] | ||
31 | ;;;; | ||
32 | (if (not (member "%GUILE_SITE%" %load-path)) | ||
33 | (set! %load-path (cons "%GUILE_SITE%" %load-path))) | ||
34 | (use-modules (ice-9 getopt-long) | ||
35 | (ice-9 rdelim) | ||
36 | (mailutils sieve-core)) | ||
37 | |||
38 | (set! sieve-parser #t) | ||
39 | |||
40 | (define sieve-debug 0) | ||
41 | (define sieve-libdir "%LIBDIR%") | ||
42 | (define sieve-load-files '()) | ||
43 | (define sieve-script-args '()) | ||
44 | (define request-verbose #f) | ||
45 | |||
46 | (define error-count 0) | ||
47 | (define current-token #f) | ||
48 | (define putback-list '()) | ||
49 | (define input-port #f) | ||
50 | (define input-file "") | ||
51 | (define input-line-number 0) | ||
52 | (define input-line "") | ||
53 | (define input-index 0) | ||
54 | (define input-length 0) | ||
55 | (define nesting-level 0) | ||
56 | (define recovery-line-number -1) | ||
57 | (define recovery-index -1) | ||
58 | |||
59 | (define (DEBUG level . rest) | ||
60 | (if (>= sieve-debug level) | ||
61 | (begin | ||
62 | (display "DEBUG(") | ||
63 | (display level) | ||
64 | (display "):") | ||
65 | (for-each (lambda (x) | ||
66 | (display x)) | ||
67 | rest) | ||
68 | (newline)))) | ||
69 | |||
70 | ;;;; Lexical scanner | ||
71 | (define (delimiter? c) | ||
72 | (or (member c (list #\[ #\] #\{ #\} #\, #\; #\( #\))) | ||
73 | (char-whitespace? c))) | ||
74 | |||
75 | (define (lex-error . rest) | ||
76 | (set! error-count (1+ error-count)) | ||
77 | (with-output-to-port | ||
78 | (current-error-port) | ||
79 | (lambda () | ||
80 | (display input-file) | ||
81 | (display ":") | ||
82 | (display input-line-number) | ||
83 | (display ": ") | ||
84 | (for-each (lambda (x) | ||
85 | (display x)) | ||
86 | rest) | ||
87 | (newline))) | ||
88 | #t) | ||
89 | |||
90 | (define (syntax-error . rest) | ||
91 | (set! error-count (1+ error-count)) | ||
92 | (with-output-to-port | ||
93 | (current-error-port) | ||
94 | (lambda () | ||
95 | (display input-file) | ||
96 | (display ":") | ||
97 | (display input-line-number) | ||
98 | (display ": ") | ||
99 | (for-each (lambda (x) | ||
100 | (display x)) | ||
101 | rest) | ||
102 | (newline))) | ||
103 | (throw 'syntax-error)) | ||
104 | |||
105 | ;;; If current input position points to end of line or to a start of | ||
106 | ;;; # comment, return #f. Otherwise return cons whose car contains | ||
107 | ;;; token type and cdr contains token itself (string). | ||
108 | (define (next-token) | ||
109 | (let ((start (do ((i input-index (1+ i))) | ||
110 | ((or (>= i input-length) | ||
111 | (not (char-whitespace? (string-ref input-line i)))) | ||
112 | i)))) | ||
113 | ; (DEBUG 100 "START " start ": " (substring input-line start)) | ||
114 | (if (< start input-length) | ||
115 | (let ((char (string-ref input-line start))) | ||
116 | (DEBUG 100 "CHAR " char) | ||
117 | (case char | ||
118 | ((#\#) | ||
119 | (set! input-index input-length) | ||
120 | #f) | ||
121 | ((#\[ #\] #\{ #\} #\( #\) #\, #\;) | ||
122 | (set! input-index (1+ start)) | ||
123 | (cons 'delimiter char)) | ||
124 | ((#\") | ||
125 | (let ((end (do ((end (1+ start) (1+ end))) | ||
126 | ((or (>= end input-length) | ||
127 | (char=? (string-ref input-line end) #\")) | ||
128 | end)))) | ||
129 | (if (>= end input-length) | ||
130 | (lex-error "Unterminated string constant")) | ||
131 | (set! input-index (1+ end)) | ||
132 | (cons 'string (substring input-line (1+ start) end)))) | ||
133 | (else | ||
134 | (DEBUG 100 "MATCH else") | ||
135 | (cond | ||
136 | ((and (char=? (string-ref input-line start) #\/) | ||
137 | (< (1+ start) input-length) | ||
138 | (char=? (string-ref input-line (1+ start)) #\*)) | ||
139 | (set! input-index (+ start 2)) | ||
140 | (cons 'bracket-comment "/*")) | ||
141 | ((char-numeric? char) | ||
142 | (let* ((end (do ((end start (1+ end))) | ||
143 | ((or | ||
144 | (>= end (1- input-length)) | ||
145 | (not (char-numeric? | ||
146 | (string-ref input-line end)))) | ||
147 | |||
148 | (cond | ||
149 | ((char-numeric? (string-ref input-line end)) | ||
150 | (1+ end)) | ||
151 | (else | ||
152 | end))))) | ||
153 | (num (string->number (substring input-line start end))) | ||
154 | (q (if (< end input-length) | ||
155 | (string-ref input-line end) | ||
156 | #f)) | ||
157 | (k 1)) | ||
158 | (case q | ||
159 | ((#f) #f) ;; nothing | ||
160 | ((#\K) | ||
161 | (set! end (1+ end)) | ||
162 | (set! k 1024)) | ||
163 | ((#\M) | ||
164 | (set! end (1+ end)) | ||
165 | (set! k 1048576)) | ||
166 | ((#\G) | ||
167 | (set! end (1+ end)) | ||
168 | (set! k 1073741824)) | ||
169 | (else | ||
170 | (if (not (delimiter? q)) | ||
171 | (lex-error "Unknown qualifier (" q ")")))) | ||
172 | (set! input-index end) | ||
173 | (cons 'number (* num k)))) | ||
174 | (else | ||
175 | (let ((end (do ((end start (1+ end))) | ||
176 | ((or (>= end input-length) | ||
177 | (delimiter? (string-ref input-line end))) | ||
178 | end)))) | ||
179 | (DEBUG 100 "END " end) | ||
180 | (set! input-index end) | ||
181 | (cond | ||
182 | ((char=? char #\:) | ||
183 | (cons 'tag (substring input-line (1+ start) end))) | ||
184 | (else | ||
185 | (cons 'identifier (substring input-line start end)))))))))) | ||
186 | #f))) | ||
187 | |||
188 | (define (end-of-line?) | ||
189 | (do ((i input-index (1+ i))) | ||
190 | ((or (>= i input-length) | ||
191 | (not (char-whitespace? (string-ref input-line i)))) | ||
192 | (>= i input-length)))) | ||
193 | |||
194 | (define (read-input-line port) | ||
195 | (set! input-line (read-line port)) | ||
196 | (if (not (eof-object? input-line)) | ||
197 | (begin | ||
198 | (set! input-line-number (1+ input-line-number)) | ||
199 | (set! input-length (string-length input-line)) | ||
200 | (set! input-index 0))) | ||
201 | input-line) | ||
202 | |||
203 | (define (next-token-from-port port) | ||
204 | (let ((tok (or (next-token) | ||
205 | (begin | ||
206 | (DEBUG 100 "2nd") | ||
207 | (set! input-line (read-line port)) | ||
208 | (if (not (eof-object? input-line)) | ||
209 | (begin | ||
210 | (set! input-line-number (1+ input-line-number)) | ||
211 | (set! input-length (string-length input-line)) | ||
212 | (set! input-index 0) | ||
213 | (next-token)) | ||
214 | input-line))))) | ||
215 | (cond | ||
216 | ((or (not tok) (eof-object? tok)) | ||
217 | tok) | ||
218 | ((and (eq? (car tok) 'identifier) | ||
219 | (string=? (cdr tok) "text:") | ||
220 | (end-of-line?)) | ||
221 | (let ((text "") | ||
222 | (string-start input-line-number)) | ||
223 | (do ((line (read-line port) (read-line port))) | ||
224 | ((or (and | ||
225 | (eof-object? line) | ||
226 | (lex-error | ||
227 | "Unexpected end of file in multiline string started on line " | ||
228 | string-start) | ||
229 | (throw 'end-of-file)) | ||
230 | (let ((len (string-length line))) | ||
231 | (and (> len 0) | ||
232 | (char=? (string-ref line 0) #\.) | ||
233 | (do ((i 1 (1+ i))) | ||
234 | ((or (>= i len) | ||
235 | (not | ||
236 | (char-whitespace? | ||
237 | (string-ref line i)))) | ||
238 | (>= i len)))))) | ||
239 | #f) | ||
240 | (set! input-line-number (1+ input-line-number)) | ||
241 | (if (and (not (string-null? line)) | ||
242 | (char=? (string-ref line 0) #\.) | ||
243 | (char=? (string-ref line 1) #\.)) | ||
244 | (set! line (substring line 1))) | ||
245 | (set! text (string-append text "\n" line))) | ||
246 | (set! input-length 0) | ||
247 | (set! input-index 0) | ||
248 | (cons 'string text))) | ||
249 | ((eq? (car tok) 'bracket-comment) | ||
250 | (let ((comment-start input-line-number)) | ||
251 | (set! input-length (- input-length input-index)) | ||
252 | (if (> input-length 0) | ||
253 | (begin | ||
254 | (set! input-line | ||
255 | (substring input-line input-index input-length)) | ||
256 | (set! input-index 0)) | ||
257 | (read-input-line port)) | ||
258 | (do () | ||
259 | ((> input-index 0) #f) | ||
260 | (cond | ||
261 | ((eof-object? input-line) | ||
262 | (lex-error | ||
263 | "Unexpected end of file in comment started on line " | ||
264 | comment-start) | ||
265 | (throw 'end-of-file)) | ||
266 | (else | ||
267 | (let ((t (string-index input-line #\*))) | ||
268 | (if (and t | ||
269 | (< (1+ t) input-length) | ||
270 | (char=? (string-ref input-line (1+ t)) #\/)) | ||
271 | (set! input-index (+ t 2)) | ||
272 | (read-input-line port)))))))) | ||
273 | (else | ||
274 | tok)))) | ||
275 | |||
276 | (define (delimiter token c) | ||
277 | (and (pair? token) | ||
278 | (eq? (car token) 'delimiter) | ||
279 | (char=? (cdr token) c))) | ||
280 | |||
281 | (define (identifier token c) | ||
282 | (and (eq? (car token) 'identifier) | ||
283 | (string=? (cdr token) c))) | ||
284 | |||
285 | (define (putback-token) | ||
286 | (set! putback-list (append (list current-token) | ||
287 | putback-list))) | ||
288 | |||
289 | (define (read-token) | ||
290 | (cond | ||
291 | ((not (null? putback-list)) | ||
292 | (set! current-token (car putback-list)) | ||
293 | (set! putback-list (cdr putback-list))) | ||
294 | (else | ||
295 | (set! current-token (do ((token (next-token-from-port input-port) | ||
296 | (next-token-from-port input-port))) | ||
297 | (token token))))) | ||
298 | current-token) | ||
299 | |||
300 | (define (require-semicolon . read) | ||
301 | (if (null? read) | ||
302 | (read-token)) | ||
303 | (if (or (eof-object? current-token) | ||
304 | (not (delimiter current-token #\;))) | ||
305 | (syntax-error "Missing ;") | ||
306 | current-token)) | ||
307 | |||
308 | (define (require-tag . read) | ||
309 | (if (null? read) | ||
310 | (read-token)) | ||
311 | (cond | ||
312 | ((eof-object? current-token) | ||
313 | (syntax-error "Expected tag but found " current-token)) | ||
314 | ((not (eq? (car current-token) 'tag)) | ||
315 | (syntax-error "Expected tag but found " (car current-token)))) | ||
316 | current-token) | ||
317 | |||
318 | (define (require-string . read) | ||
319 | (if (null? read) | ||
320 | (read-token)) | ||
321 | (cond | ||
322 | ((eof-object? current-token) | ||
323 | (syntax-error "Expected string but found " current-token)) | ||
324 | ((not (eq? (car current-token) 'string)) | ||
325 | (syntax-error "Expected string but found " (car current-token)))) | ||
326 | current-token) | ||
327 | |||
328 | (define (require-number . read) | ||
329 | (if (null? read) | ||
330 | (read-token)) | ||
331 | (cond | ||
332 | ((eof-object? current-token) | ||
333 | (syntax-error "Expected number but found " current-token)) | ||
334 | ((not (eq? (car current-token) 'number)) | ||
335 | (syntax-error "Expected number but found " (car current-token)))) | ||
336 | current-token) | ||
337 | |||
338 | (define (require-string-list . read) | ||
339 | (if (null? read) | ||
340 | (read-token)) | ||
341 | (cond | ||
342 | ((eof-object? current-token) | ||
343 | (syntax-error "Expected string-list but found " current-token)) | ||
344 | ((eq? (car current-token) 'string) | ||
345 | (list 'string-list (cdr current-token))) | ||
346 | ((not (eq? (car current-token) 'delimiter)) | ||
347 | (syntax-error "Expected string-list but found " (car current-token))) | ||
348 | ((char=? (cdr current-token) #\[) | ||
349 | (do ((slist '()) | ||
350 | (token (read-token) (read-token))) | ||
351 | ((if (not (eq? (car token) 'string)) | ||
352 | (begin | ||
353 | (syntax-error "Expected string but found " (car token)) | ||
354 | #t) | ||
355 | (begin | ||
356 | (set! slist (append slist (list (cdr token)))) | ||
357 | (read-token) | ||
358 | (cond | ||
359 | ((eof-object? current-token) | ||
360 | (syntax-error "Unexpected end of file in string list") | ||
361 | #t) ;; break; | ||
362 | ((eq? (car current-token) 'delimiter) | ||
363 | (cond | ||
364 | ((char=? (cdr current-token) #\,) #f) ;; continue | ||
365 | ((char=? (cdr current-token) #\]) #t) ;; break | ||
366 | (else | ||
367 | (lex-error "Expected ',' or ']' but found " | ||
368 | (cdr current-token)) | ||
369 | #t))) | ||
370 | (else | ||
371 | (lex-error "Expected delimiter but found " | ||
372 | (car current-token)) | ||
373 | #t)))) | ||
374 | (cons 'string-list slist)))) | ||
375 | (else | ||
376 | (syntax-error "Expected '[' but found " (car current-token))))) | ||
377 | |||
378 | (define (require-identifier . read) | ||
379 | (if (null? read) | ||
380 | (read-token)) | ||
381 | (cond | ||
382 | ((eof-object? current-token) | ||
383 | (syntax-error "1. Expected identifier but found " current-token)) | ||
384 | ((not (eq? (car current-token) 'identifier)) | ||
385 | (syntax-error "2. Expected identifier but found " (car current-token)))) | ||
386 | current-token) | ||
387 | |||
388 | ;;;; | ||
389 | |||
390 | ;;; Syntax tables. | ||
391 | ;;; A syntax table is a list of | ||
392 | ;;; Each entry is: (list NAME FUNCTION OPT-ARG-LIST REQ-ARG-LIST) | ||
393 | ;;; NAME is a string representing the input language keyword, | ||
394 | ;;; FUNCTION is a corresponding function: | ||
395 | ;;; (define (foo [arg [arg...]] . opt-args) | ||
396 | ;;; OPT-ARG-LIST is a list of optional arguments (tags), | ||
397 | ;;; REQ-ARG-LIST is a list of required (positional) arguments | ||
398 | |||
399 | (define (sieve-syntax-table-lookup table name) | ||
400 | (let ((entry (assoc name table))) | ||
401 | (if entry | ||
402 | (cdr entry) | ||
403 | #f))) | ||
404 | |||
405 | (define-macro (sieve-syntax-table-add table name function req-arg-list opt-arg-list) | ||
406 | `(cond | ||
407 | ((not (list? ,opt-arg-list)) | ||
408 | (lex-error "sieve-syntax-table-add: opt-arg-list must be a list")) | ||
409 | ((not (list? ,req-arg-list)) | ||
410 | (lex-error "sieve-syntax-table-add: req-arg-list must be a list")) | ||
411 | ((not (or (eq? ,function #f) | ||
412 | (eq? ,function #t) | ||
413 | (procedure? ,function))) | ||
414 | (lex-error "sieve-syntax-table-add: bad type for function " ,function)) | ||
415 | (else | ||
416 | (set! ,table | ||
417 | (append ,table | ||
418 | (list | ||
419 | (list ,name ,function ,opt-arg-list ,req-arg-list))))))) | ||
420 | |||
421 | ;;;; | ||
422 | |||
423 | (defmacro do-for-all (fun rest) | ||
424 | `(for-each | ||
425 | (lambda (x) | ||
426 | (apply ,fun x)) | ||
427 | ,rest)) | ||
428 | |||
429 | |||
430 | ;;;; Available syntax tables. | ||
431 | |||
432 | ;;;; Comparators | ||
433 | |||
434 | ;;; Syntax table for comparators. The opt-arg-list and req-arg-list have | ||
435 | ;;; no meaning for comparators, so they are ignored. The handler function | ||
436 | ;;; names must start with "comparator-" | ||
437 | (define sieve-comparator-table '()) | ||
438 | |||
439 | (define (sieve-find-comparator name) | ||
440 | (sieve-syntax-table-lookup sieve-comparator-table name)) | ||
441 | |||
442 | (define (sieve-register-comparator name function) | ||
443 | (sieve-syntax-table-add sieve-comparator-table name function '() '())) | ||
444 | |||
445 | ;;; Register standard comparators | ||
446 | (do-for-all sieve-register-comparator sieve-standard-comparators) | ||
447 | |||
448 | ;;;; Sieve Tests | ||
449 | |||
450 | ;;; Syntax table for tests. Function names must start with "test-" | ||
451 | (define sieve-test-table '()) | ||
452 | |||
453 | (define (sieve-find-test name) | ||
454 | (sieve-syntax-table-lookup sieve-test-table name)) | ||
455 | |||
456 | (define (sieve-register-test name function req-arg-list opt-arg-list) | ||
457 | (sieve-syntax-table-add sieve-test-table name function | ||
458 | req-arg-list opt-arg-list)) | ||
459 | |||
460 | ;;; Register standard tests | ||
461 | (do-for-all sieve-register-test sieve-standard-tests) | ||
462 | |||
463 | ;;;; Sieve Actions | ||
464 | |||
465 | ;;; Syntax table for actions. Function names start with "action-" | ||
466 | (define sieve-action-table '()) | ||
467 | |||
468 | (define (sieve-find-action name) | ||
469 | (sieve-syntax-table-lookup sieve-action-table name)) | ||
470 | |||
471 | (define (sieve-register-action name function req-arg-list opt-arg-list) | ||
472 | (sieve-syntax-table-add sieve-action-table name function | ||
473 | req-arg-list opt-arg-list)) | ||
474 | |||
475 | ;;; Register standard actions | ||
476 | (do-for-all sieve-register-action sieve-standard-actions) | ||
477 | |||
478 | ;;;; | ||
479 | |||
480 | ;;;; Command parsers | ||
481 | |||
482 | ;;; sieve-preprocess-arguments: Preprocess and group the arguments. Returns | ||
483 | ;;; a cons whose car is a list of all optional arguments, and the cdr is | ||
484 | ;;; a list of the rest of the arguments. | ||
485 | ;;; | ||
486 | ;;; arguments = *argument [test / test-list] | ||
487 | ;;; argument = string-list / number / tag | ||
488 | |||
489 | (define (sieve-preprocess-arguments tag-gram) | ||
490 | (do ((opt-list '()) ;; List of optional arguments (tags) | ||
491 | (arg-list '()) ;; List of positional arguments | ||
492 | (last-tag #f) ;; Description of the last tag from tag-gram | ||
493 | (state 'opt) ;; 'opt when scanning optional arguments, | ||
494 | ;; 'arg when scanning positional arguments | ||
495 | (token (read-token) (read-token))) ;; Obtain next token | ||
496 | ((cond | ||
497 | ((eof-object? token) | ||
498 | (syntax-error "Expected argument but found " token)) | ||
499 | ((eq? (car token) 'tag) | ||
500 | (if (not (eq? state 'opt)) | ||
501 | (syntax-error "Misplaced tag: :" (cdr token))) | ||
502 | (set! last-tag (assoc (cdr token) tag-gram)) | ||
503 | (if (not last-tag) | ||
504 | (syntax-error | ||
505 | "Tag :" (cdr token) " is not allowed in this context")) | ||
506 | (set! opt-list (append opt-list (list token))) | ||
507 | #f) | ||
508 | ((or (eq? (car token) 'number) | ||
509 | (eq? (car token) 'string)) | ||
510 | (cond | ||
511 | ((and (eq? state 'opt) (pair? last-tag)) | ||
512 | (cond | ||
513 | ((cdr last-tag) | ||
514 | (if (not (eq? (cdr last-tag) (car token))) | ||
515 | (syntax-error | ||
516 | "Tag :" (car last-tag) " takes " (cdr last-tag) " argument")) | ||
517 | (cond | ||
518 | ((string=? (car last-tag) "comparator") | ||
519 | (let ((comp (sieve-find-comparator (cdr token)))) | ||
520 | (if (not comp) | ||
521 | (syntax-error "Undefined comparator: " (cdr token))) | ||
522 | (set-cdr! token (car comp))))) | ||
523 | (set! opt-list (append opt-list (list token))) | ||
524 | (set! last-tag #f)) | ||
525 | (else | ||
526 | (set! state 'arg) | ||
527 | (set! arg-list (append arg-list (list token)))))) | ||
528 | (else | ||
529 | (set! arg-list (append arg-list (list token))))) | ||
530 | #f) | ||
531 | ((delimiter token #\[) | ||
532 | (putback-token) | ||
533 | (cond | ||
534 | ((and (eq? state 'opt) (pair? last-tag)) | ||
535 | (cond | ||
536 | ((cdr last-tag) | ||
537 | (if (not (eq? (cdr last-tag) 'string-list)) | ||
538 | (syntax-error | ||
539 | "Tag :" (car last-tag) " takes string list argument")) | ||
540 | (set! opt-list (append opt-list (list (require-string-list)))) | ||
541 | (set! last-tag #f)) | ||
542 | (else | ||
543 | (set! state 'arg) | ||
544 | (set! arg-list (append arg-list (list (require-string-list))))))) | ||
545 | (else | ||
546 | (set! arg-list (append arg-list (list (require-string-list)))))) | ||
547 | #f) | ||
548 | (else | ||
549 | #t)) | ||
550 | (cons opt-list arg-list)))) | ||
551 | |||
552 | ;;; sieve-parse-arguments: Parse the arguments to a test or an action. | ||
553 | ;;; ENTRY is the syntax table entry to guide the parsing | ||
554 | ;;; | ||
555 | (define (sieve-parse-arguments ident entry) | ||
556 | (DEBUG 100 "sieve-parse-arguments" entry) | ||
557 | (let ((arg-list (sieve-preprocess-arguments (car (cdr entry))))) | ||
558 | ;; Process positional arguments | ||
559 | (do ((expect (car (cdr (cdr entry))) (cdr expect)) | ||
560 | (argl (cdr arg-list) (cdr argl)) | ||
561 | (n 1 (1+ n))) | ||
562 | ((cond | ||
563 | ((null? expect) | ||
564 | (if (not (null? argl)) | ||
565 | (syntax-error | ||
566 | "Too many positional arguments for " ident | ||
567 | " (bailed out at " (car argl) ")")) | ||
568 | #t) | ||
569 | ((null? argl) | ||
570 | (if (not (null? expect)) | ||
571 | (syntax-error | ||
572 | "Too few positional arguments for " ident)) | ||
573 | #t) | ||
574 | (else #f)) #f) | ||
575 | (let ((expect-type (car expect)) | ||
576 | (arg (car argl))) | ||
577 | (cond | ||
578 | ((and (eq? expect-type 'string-list) | ||
579 | (eq? (car arg) 'string)) | ||
580 | ;; Coerce string to string-list | ||
581 | (sieve-exp-append (list 'list (cdr arg)))) | ||
582 | ((eq? expect-type (car arg)) | ||
583 | (if (eq? expect-type 'string-list) | ||
584 | (sieve-exp-append (append (list 'list) (cdr arg))) | ||
585 | (sieve-exp-append (cdr arg)))) | ||
586 | (else | ||
587 | (syntax-error | ||
588 | "Type mismatch in argument " n " to " (cdr ident) | ||
589 | "; expected " expect-type ", but got " (car arg)))))) | ||
590 | ;; Process optional arguments (tags). | ||
591 | ;; They have already been tested | ||
592 | (for-each | ||
593 | (lambda (tag) | ||
594 | (sieve-exp-append (cond | ||
595 | ((eq? (car tag) 'tag) | ||
596 | (symbol->keyword | ||
597 | (string->symbol (cdr tag)))) | ||
598 | ((eq? (car tag) 'string-list) | ||
599 | (append (list 'list) (cdr tag))) | ||
600 | (else | ||
601 | (cdr tag))))) | ||
602 | (car arg-list)))) | ||
603 | |||
604 | ;;;; | ||
605 | |||
606 | ;;;; Parser functions for tests | ||
607 | |||
608 | ;;; test-list = "(" test *("," test) ")" | ||
609 | (define (sieve-parse-test-list) | ||
610 | (do ((token (sieve-parse-test) (sieve-parse-test))) | ||
611 | ((cond | ||
612 | ((delimiter token #\)) | ||
613 | #t) ;; break; | ||
614 | ((delimiter token #\,) | ||
615 | #f) ;; continue | ||
616 | ((eof-object? token) | ||
617 | (syntax-error "Unexpected end of file in test-list") | ||
618 | #t) ;; break | ||
619 | (else | ||
620 | (syntax-error "Expected ',' or ')' but found " (cdr token)) | ||
621 | #t)) ;; break | ||
622 | (read-token)))) | ||
623 | |||
624 | ;;; test = identifier arguments | ||
625 | (define (sieve-parse-test) | ||
626 | (let ((ident (require-identifier))) | ||
627 | (cond | ||
628 | ((string=? (cdr ident) "not") | ||
629 | (sieve-exp-begin) | ||
630 | (sieve-exp-append 'not) | ||
631 | (sieve-parse-test) | ||
632 | (sieve-exp-finish)) | ||
633 | (else | ||
634 | (read-token) | ||
635 | (cond | ||
636 | ((eof-object? current-token) | ||
637 | (syntax-error "Unexpected end of file in conditional")) | ||
638 | ((delimiter current-token #\() | ||
639 | (sieve-exp-begin) | ||
640 | (cond | ||
641 | ((string=? (cdr ident) "allof") | ||
642 | (sieve-exp-append 'and)) | ||
643 | ((string=? (cdr ident) "anyof") | ||
644 | (sieve-exp-append 'or)) | ||
645 | (else | ||
646 | (syntax-error "Unexpected '('"))) | ||
647 | (sieve-parse-test-list) | ||
648 | (sieve-exp-finish)) | ||
649 | (else | ||
650 | (let ((test (sieve-find-test (cdr ident)))) | ||
651 | (if (not test) | ||
652 | (syntax-error "Unknown test name: " (cdr ident))) | ||
653 | (cond | ||
654 | ((procedure? (car test)) | ||
655 | (putback-token) | ||
656 | (sieve-exp-begin) | ||
657 | (sieve-exp-append (car test)) | ||
658 | (sieve-parse-arguments (cdr ident) test) | ||
659 | (sieve-exp-finish)) | ||
660 | (else | ||
661 | (sieve-exp-append (car test)))))))))) | ||
662 | current-token) | ||
663 | |||
664 | (define (sieve-parse-block . read) | ||
665 | (if (not (null? read)) | ||
666 | (read-token)) | ||
667 | (if (delimiter current-token #\{) | ||
668 | (begin | ||
669 | (set! nesting-level (1+ nesting-level)) | ||
670 | (do ((token (read-token) (read-token))) | ||
671 | ((cond | ||
672 | ((eof-object? token) | ||
673 | (syntax-error "Unexpected end of file in block") | ||
674 | #t) | ||
675 | ((delimiter token #\}) | ||
676 | #t) | ||
677 | (else | ||
678 | (putback-token) | ||
679 | (sieve-parse-command) | ||
680 | #f))) #f) | ||
681 | (set! nesting-level (1- nesting-level))) | ||
682 | (require-semicolon 'dont-read))) | ||
683 | |||
684 | ;;; if <test1: test> <block1: block> | ||
685 | (define (sieve-parse-if-internal) | ||
686 | (DEBUG 10 "sieve-parse-if-internal" current-token) | ||
687 | (sieve-exp-begin) | ||
688 | |||
689 | (sieve-parse-test) | ||
690 | |||
691 | (sieve-parse-block) | ||
692 | (sieve-exp-finish) | ||
693 | |||
694 | (read-token) | ||
695 | (cond | ||
696 | ((eof-object? current-token) ) | ||
697 | ((identifier current-token "elsif") | ||
698 | (sieve-parse-if-internal)) | ||
699 | ((identifier current-token "else") | ||
700 | (sieve-exp-begin 'else) | ||
701 | (sieve-parse-block 'read) | ||
702 | (sieve-exp-finish)) | ||
703 | (else | ||
704 | (putback-token)))) | ||
705 | |||
706 | (define (sieve-parse-if) | ||
707 | (sieve-exp-begin 'cond) | ||
708 | (sieve-parse-if-internal) | ||
709 | (sieve-exp-finish)) | ||
710 | |||
711 | (define (sieve-parse-else) | ||
712 | (syntax-error "else without if")) | ||
713 | |||
714 | (define (sieve-parse-elsif) | ||
715 | (syntax-error "elsif without if")) | ||
716 | |||
717 | ;;; require <capabilities: string-list> | ||
718 | (define (sieve-parse-require) | ||
719 | (for-each | ||
720 | (lambda (capability) | ||
721 | (if (not | ||
722 | (cond | ||
723 | ((and | ||
724 | (>= (string-length capability) 5) | ||
725 | (string=? (substring capability 0 5) "test-")) | ||
726 | (sieve-find-test (substring capability 5))) | ||
727 | ((and | ||
728 | (>= (string-length capability) 11) | ||
729 | (string=? (substring capability 0 11) "comparator-")) | ||
730 | (sieve-find-comparator (substring capability 11))) | ||
731 | (else | ||
732 | (sieve-find-action capability)))) | ||
733 | (let ((name (string-append sieve-libdir | ||
734 | "/" capability ".scm"))) | ||
735 | (set! sieve-load-files (append sieve-load-files (list name))) | ||
736 | (catch #t | ||
737 | (lambda () | ||
738 | (load name)) | ||
739 | (lambda args | ||
740 | (lex-error "Can't load required capability " | ||
741 | capability) | ||
742 | args))))) | ||
743 | (cdr (require-string-list))) | ||
744 | (require-semicolon)) | ||
745 | |||
746 | ;;; stop | ||
747 | (define (sieve-parse-stop) | ||
748 | (sieve-exp-begin sieve-stop) | ||
749 | (sieve-exp-finish) | ||
750 | (require-semicolon)) | ||
751 | |||
752 | ;;;; | ||
753 | |||
754 | ;;;; Parser functions for actions | ||
755 | |||
756 | (define (sieve-parse-action) | ||
757 | (let* ((name (cdr current-token)) | ||
758 | (descr (sieve-find-action name))) | ||
759 | (cond | ||
760 | (descr | ||
761 | (cond | ||
762 | ((car descr) | ||
763 | (sieve-exp-begin 'reg-action) | ||
764 | (sieve-exp-finish) | ||
765 | (sieve-exp-begin (car descr)) | ||
766 | (sieve-parse-arguments name descr) | ||
767 | (require-semicolon 'dont-read) | ||
768 | (sieve-exp-finish)) | ||
769 | (else | ||
770 | (require-semicolon)))) | ||
771 | (else | ||
772 | (syntax-error "Unknown identifier: " name))))) | ||
773 | |||
774 | ;;;; | ||
775 | |||
776 | ;;;; The parser | ||
777 | |||
778 | (define (sieve-parse-command) | ||
779 | (DEBUG 10 "sieve-parse-command" current-token) | ||
780 | (catch 'syntax-error | ||
781 | (lambda () | ||
782 | (read-token) | ||
783 | (cond | ||
784 | ((or (not current-token) | ||
785 | (eof-object? current-token))) ;; Skip comments and #<eof> | ||
786 | ((eq? (car current-token) 'identifier) | ||
787 | ;; Process a command | ||
788 | (let ((elt (assoc (string->symbol (cdr current-token)) | ||
789 | (list | ||
790 | (cons 'if sieve-parse-if) | ||
791 | (cons 'elsif sieve-parse-elsif) | ||
792 | (cons 'else sieve-parse-else) | ||
793 | (cons 'require sieve-parse-require) | ||
794 | (cons 'stop sieve-parse-stop))))) | ||
795 | (if (not elt) | ||
796 | (sieve-parse-action) | ||
797 | (apply (cdr elt) '())))) | ||
798 | (else | ||
799 | (syntax-error "3. Expected identifier but found " | ||
800 | (cdr current-token))))) | ||
801 | (lambda args | ||
802 | ;; Error recovery: skip until we find a ';' or '}'. | ||
803 | (if (and (= input-line-number recovery-line-number) | ||
804 | (= input-index recovery-index)) | ||
805 | (begin | ||
806 | (lex-error "ERROR RECOVERY: Skipping to end of file") | ||
807 | (throw 'end-of-file))) | ||
808 | (set! recovery-line-number input-line-number) | ||
809 | (set! recovery-index input-index) | ||
810 | |||
811 | (if (or (delimiter current-token #\}) | ||
812 | (delimiter current-token #\;)) | ||
813 | (read-token)) | ||
814 | (DEBUG 50 "ERROR RECOVERY at " current-token) | ||
815 | (do ((token current-token (read-token))) | ||
816 | ((cond | ||
817 | ((eof-object? token) | ||
818 | (throw 'end-of-file)) | ||
819 | ((delimiter token #\;) | ||
820 | #t) | ||
821 | ((delimiter token #\}) | ||
822 | (cond | ||
823 | ((> nesting-level 0) | ||
824 | (putback-token) | ||
825 | #t) | ||
826 | (else | ||
827 | #f))) | ||
828 | ((delimiter token #\{) | ||
829 | (sieve-skip-block) | ||
830 | (putback-token) | ||
831 | #f) | ||
832 | (else | ||
833 | #f)) #f)) | ||
834 | (DEBUG 50 "ERROR RECOVERY FINISHED AT " current-token))) | ||
835 | current-token) | ||
836 | |||
837 | (define (sieve-skip-block) | ||
838 | (do ((token (read-token) (read-token))) | ||
839 | ((cond | ||
840 | ((eof-object? token) | ||
841 | (throw 'end-of-file)) | ||
842 | ((delimiter token #\{) | ||
843 | (sieve-skip-block) | ||
844 | #f) | ||
845 | ((delimiter token #\}) | ||
846 | #t) | ||
847 | (else | ||
848 | #f)) #f))) | ||
849 | |||
850 | (define (sieve-parse-from-port port) | ||
851 | (set! input-port port) | ||
852 | (do ((token (sieve-parse-command) (sieve-parse-command))) | ||
853 | ((eof-object? token) #f)) ) | ||
854 | |||
855 | (define (sieve-parse filename) | ||
856 | (if (file-exists? filename) | ||
857 | (catch 'end-of-file | ||
858 | (lambda () | ||
859 | (set! error-count 0) | ||
860 | (set! current-token #f) | ||
861 | (set! input-file filename) | ||
862 | (set! input-line-number 0) | ||
863 | (set! putback-list '()) | ||
864 | (call-with-input-file filename sieve-parse-from-port)) | ||
865 | (lambda args args)))) | ||
866 | |||
867 | ;;;; | ||
868 | |||
869 | ;;;; Code generator | ||
870 | |||
871 | (define sieve-exp '()) ;; Expression currently being built | ||
872 | (define sieve-exp-stack '()) | ||
873 | (define sieve-code-list '()) ;; Resulting scheme code | ||
874 | |||
875 | (define (sieve-exp-begin . exp) | ||
876 | (set! sieve-exp-stack (append (list sieve-exp) sieve-exp-stack)) | ||
877 | (set! sieve-exp exp)) | ||
878 | |||
879 | (define (sieve-exp-append exp) | ||
880 | (set! sieve-exp (append sieve-exp (list exp)))) | ||
881 | |||
882 | (define (sieve-exp-finish) | ||
883 | (set! sieve-exp (append (car sieve-exp-stack) (list sieve-exp))) | ||
884 | (set! sieve-exp-stack (cdr sieve-exp-stack))) | ||
885 | |||
886 | (define (sieve-code-begin) | ||
887 | (set! sieve-exp-stack '()) | ||
888 | (set! sieve-exp '())) | ||
889 | |||
890 | (define (sieve-code-prologue code-list) | ||
891 | (sieve-exp-begin (car code-list)) | ||
892 | (let loop ((code-list (cdr code-list))) | ||
893 | (for-each | ||
894 | (lambda (elt) | ||
895 | (cond | ||
896 | ((and (list? elt) (not (null? elt))) | ||
897 | (sieve-exp-begin (car elt)) | ||
898 | (loop (cdr elt)) | ||
899 | (sieve-exp-finish)) | ||
900 | (else | ||
901 | (sieve-exp-append elt)))) | ||
902 | code-list))) | ||
903 | |||
904 | (define (sieve-code-finish) | ||
905 | (if (not (null? sieve-exp)) | ||
906 | (set! sieve-code-list (append sieve-code-list sieve-exp)))) | ||
907 | |||
908 | ;;; Print the program | ||
909 | |||
910 | (define (sieve-code-print-list exp) | ||
911 | (display "(") | ||
912 | (for-each | ||
913 | (lambda (x) | ||
914 | (cond | ||
915 | ((procedure? x) | ||
916 | (display (procedure-name x))) | ||
917 | ((list? x) | ||
918 | (sieve-code-print-list x)) | ||
919 | (else | ||
920 | (write x))) | ||
921 | (display " ")) | ||
922 | exp) | ||
923 | (display ")")) | ||
924 | |||
925 | ;;; Save the program | ||
926 | |||
927 | (define (sieve-save-program outfile) | ||
928 | (with-output-to-file | ||
929 | outfile | ||
930 | (lambda () | ||
931 | (display "#! ") | ||
932 | (display "/bin/sh\n\ | ||
933 | # aside from this initial boilerplate, this is actually -*- scheme -*- code\n\ | ||
934 | exec ${GUILE-guile} -l $0 -c '(mailutils-main)'\n") | ||
935 | (display (string-append | ||
936 | "# This Guile mailbox parser was made from " filename)) | ||
937 | (newline) | ||
938 | (display "# by sieve.scm, GNU %PACKAGE% %VERSION%\n!#") | ||
939 | (newline) | ||
940 | |||
941 | (display | ||
942 | "(if (not (member \"%GUILE_SITE%\" %load-path))\n | ||
943 | (set! %load-path (cons \"%GUILE_SITE%\" %load-path)))\n") | ||
944 | |||
945 | (display "(use-modules (mailutils sieve-core))\n") | ||
946 | (display (string-append | ||
947 | "(set! sieve-source \"" filename "\")")) | ||
948 | (newline) | ||
949 | |||
950 | (for-each | ||
951 | (lambda (file) | ||
952 | (display (string-append | ||
953 | "(load \"" file "\")")) | ||
954 | (newline)) | ||
955 | sieve-load-files) | ||
956 | (newline) | ||
957 | (if request-verbose | ||
958 | (display "(set! sieve-verbose #t)\n")) | ||
959 | (display "(define (sieve-filter-thunk) ") | ||
960 | |||
961 | (sieve-code-print-list (car sieve-code-list)) | ||
962 | (display ")\n\n") | ||
963 | |||
964 | (display "(define (mailutils-main . rest)\n") | ||
965 | (display " (sieve-main sieve-filter-thunk))\n\n") | ||
966 | |||
967 | (display "(define (mailutils-check-message msg)\n\ | ||
968 | (set! sieve-current-message msg)\n\ | ||
969 | (sieve-run-current-message sieve-filter-thunk))\n") | ||
970 | |||
971 | (display "\n\ | ||
972 | ;;;; Local Variables:\n\ | ||
973 | ;;;; buffer-read-only: t\n\ | ||
974 | ;;;; End:\n")))) | ||
975 | |||
976 | ;;;; | ||
977 | |||
978 | ;;;; Main | ||
979 | |||
980 | (define filename #f) | ||
981 | (define output #f) | ||
982 | |||
983 | (define (sieve-usage) | ||
984 | (display "usage: sieve2scm [OPTIONS] [mailbox]\n") | ||
985 | (display "GNU sieve2scm -- compile a Sieve program into Scheme code\n\n") | ||
986 | (display " -f, --file FILENAME Set input file name\n") | ||
987 | (display " -o, --output FILENAME Set output file name\n") | ||
988 | (display " -L, --lib-dir DIRNAME Set sieve library directory name\n") | ||
989 | (display " -d, --debug LEVEL Set debugging level\n") | ||
990 | (display " --version Show program version\n\n") | ||
991 | (display "If -o option is not given, the compiled program is executed\n") | ||
992 | (display "immediately. It operates on the user system mailbox unless\n") | ||
993 | (display "mailbox is given in the command line.\n") | ||
994 | (exit 0)) | ||
995 | |||
996 | (define (sieve-version) | ||
997 | (format #t "sieve2scm (~A) ~A~%" mu-package mu-version) | ||
998 | (exit 0)) | ||
999 | |||
1000 | ;;; Parse command line | ||
1001 | |||
1002 | (define grammar | ||
1003 | `((file (single-char #\f) | ||
1004 | (value #t)) | ||
1005 | (output (single-char #\o) | ||
1006 | (value #t)) | ||
1007 | (debug (single-char #\d) | ||
1008 | (value #t)) | ||
1009 | (lib-dir (single-char #\L) | ||
1010 | (value #t)) | ||
1011 | (version) | ||
1012 | (verbose (single-char #\v)) | ||
1013 | (help (single-char #\h)))) | ||
1014 | |||
1015 | (define program-name (car (command-line))) | ||
1016 | |||
1017 | (for-each | ||
1018 | (lambda (x) | ||
1019 | (cond | ||
1020 | ((pair? x) | ||
1021 | (case (car x) | ||
1022 | ((debug) | ||
1023 | (set! sieve-debug (string->number (cdr x)))) | ||
1024 | ((file) | ||
1025 | (set! filename (cdr x))) | ||
1026 | ((lib-dir) | ||
1027 | (set! sieve-libdir (cdr x))) | ||
1028 | ((output) | ||
1029 | (set! output (cdr x))) | ||
1030 | ((version) | ||
1031 | (sieve-version)) | ||
1032 | ((verbose) | ||
1033 | (set! request-verbose #t)) | ||
1034 | ((help) | ||
1035 | (sieve-usage)) | ||
1036 | ('() | ||
1037 | (set! sieve-script-args (cdr x))))))) | ||
1038 | (getopt-long (command-line) grammar)) | ||
1039 | |||
1040 | (cond | ||
1041 | ((not filename) | ||
1042 | (format (current-error-port) "~A: missing input filename~%" program-name) | ||
1043 | (sieve-usage)) | ||
1044 | ((not (file-exists? filename)) | ||
1045 | (format (current-error-port) "~A: Input file ~A does not exist~%" filename) | ||
1046 | (exit 0))) | ||
1047 | |||
1048 | (if (not sieve-libdir) | ||
1049 | (set! sieve-libdir | ||
1050 | (let ((myname (car (command-line)))) | ||
1051 | (if (not (char=? (string-ref myname 0) #\/)) | ||
1052 | (set! myname (string-append (getcwd) "/" myname))) | ||
1053 | (let ((slash (string-rindex myname #\/))) | ||
1054 | (substring myname 0 slash))))) | ||
1055 | |||
1056 | (sieve-code-prologue | ||
1057 | '(letrec | ||
1058 | ((implicit-keep #t) | ||
1059 | (reg-action (lambda () (set! implicit-keep #f)))))) | ||
1060 | |||
1061 | (sieve-parse filename) | ||
1062 | (sieve-exp-append 'implicit-keep) | ||
1063 | (sieve-exp-finish) | ||
1064 | (sieve-code-finish) | ||
1065 | |||
1066 | (cond | ||
1067 | ((> error-count 0) | ||
1068 | (display error-count) | ||
1069 | (display " errors.") | ||
1070 | (newline) | ||
1071 | (exit 1)) | ||
1072 | (output | ||
1073 | (sieve-save-program output)) | ||
1074 | (else | ||
1075 | (let ((temp-file (tmpnam)) | ||
1076 | (saved-umask (umask #o077))) | ||
1077 | (sieve-save-program temp-file) | ||
1078 | (catch #t | ||
1079 | (lambda () | ||
1080 | (set-cdr! (command-line) sieve-script-args) | ||
1081 | (load temp-file)) | ||
1082 | (lambda (key . args) | ||
1083 | (apply display-error the-last-stack (current-error-port) args))) | ||
1084 | (delete-file temp-file) | ||
1085 | (umask saved-umask)))) | ||
1086 | |||
1087 | ;;;; End of sieve.scm | ||
1088 | |||
1089 | |||
1090 |
scheme/vacation.scm
deleted
100644 → 0
1 | ;;;; GNU Mailutils -- a suite of utilities for electronic mail | ||
2 | ;;;; Copyright (C) 1999-2001, 2006-2007, 2010-2012, 2014-2017 Free | ||
3 | ;;;; Software Foundation, Inc. | ||
4 | ;;;; | ||
5 | ;;;; GNU Mailutils is free software; you can redistribute it and/or modify | ||
6 | ;;;; it under the terms of the GNU General Public License as published by | ||
7 | ;;;; the Free Software Foundation; either version 3, or (at your option) | ||
8 | ;;;; any later version. | ||
9 | ;;;; | ||
10 | ;;;; GNU Mailutils is distributed in the hope that it will be useful, | ||
11 | ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
12 | ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
13 | ;;;; GNU General Public License for more details. | ||
14 | ;;;; | ||
15 | ;;;; You should have received a copy of the GNU General Public License along | ||
16 | ;;;; with GNU Mailutils. If not, see <http://www.gnu.org/licenses/>. | ||
17 | |||
18 | ;;;; This module provides "vacation" extension | ||
19 | |||
20 | ;;; vacation example: | ||
21 | ;;; vacation :days 18 | ||
22 | ;;; :aliases ["gray@gnu.org", "gray@mirddin.farlep.net"] | ||
23 | ;;; :addresses ["bug-mailutils@gnu.org","bug-inetutils@gnu.org"] | ||
24 | ;;; :subject "I'm on vacation" | ||
25 | ;;; :mime | ||
26 | ;;; text: | ||
27 | ;;; I am on vacation until July 22. I'll attend your message as soon | ||
28 | ;;; as I'm back. | ||
29 | ;;; . | ||
30 | ;;; | ||
31 | ;;; Additionally, the :sender flag may be used to debug the script. | ||
32 | |||
33 | ;; Debugging flag | ||
34 | (define vacation-debug #f) | ||
35 | |||
36 | ;; Each entry is (cons SENDER DATE), where SENDER is the sender email | ||
37 | ;; address (lowercase) and DATE is the date where the first message | ||
38 | ;; from this sender was received. | ||
39 | (define vacation-db '()) | ||
40 | |||
41 | (define (vacation-downcase name) | ||
42 | (let ((len (string-length name))) | ||
43 | (do ((i 0 (1+ i))) | ||
44 | ((= i len) name) | ||
45 | (string-set! name i (char-downcase (string-ref name i)))))) | ||
46 | |||
47 | (define (vacation-db-name) | ||
48 | (let ((pwd (mu-getpwuid (getuid)))) | ||
49 | (string-append (vector-ref pwd 5) "/.vacation.db"))) | ||
50 | |||
51 | (define (vacation-db-load) | ||
52 | (catch #t | ||
53 | (lambda () | ||
54 | (call-with-input-file (vacation-db-name) | ||
55 | (lambda (port) | ||
56 | (set! vacation-db (read port))))) | ||
57 | (lambda args args))) | ||
58 | |||
59 | (define (vacation-db-save) | ||
60 | (catch #t | ||
61 | (lambda () | ||
62 | (let ((mask (umask #o077))) | ||
63 | (call-with-output-file (vacation-db-name) | ||
64 | (lambda (port) | ||
65 | (display ";; Vacation database file\n" port) | ||
66 | (display ";; Generated automatically. Please do not edit\n" | ||
67 | port) | ||
68 | (write vacation-db port))) | ||
69 | (umask mask))) | ||
70 | (lambda args args))) | ||
71 | |||
72 | (define (vacation-db-lookup sender days) | ||
73 | (vacation-db-load) | ||
74 | (let ((val (assoc (vacation-downcase sender) vacation-db))) | ||
75 | (cond | ||
76 | (val | ||
77 | (cond | ||
78 | ((and days (> days 0)) | ||
79 | (<= (- (car (gettimeofday)) (cdr val)) (* days 86400))) | ||
80 | (else | ||
81 | #t))) | ||
82 | (else | ||
83 | #f)))) | ||
84 | |||
85 | (define (vacation-db-update msg) | ||
86 | (let* ((sender (vacation-downcase (mu-message-get-sender msg))) | ||
87 | (date (car (gettimeofday))) | ||
88 | (val (assoc sender vacation-db))) | ||
89 | (cond | ||
90 | (val | ||
91 | (set-cdr! val date)) | ||
92 | (else | ||
93 | (set! vacation-db (append vacation-db (list | ||
94 | (cons sender date))))))) | ||
95 | (vacation-db-save)) | ||
96 | |||
97 | (define vacation-noreply-senders | ||
98 | (list | ||
99 | ".*-REQUEST@.*" | ||
100 | ".*-RELAY@.*" | ||
101 | ".*-OWNER@.*" | ||
102 | "OWNER-.*" | ||
103 | "postmaster@.*" | ||
104 | "UUCP@.*" | ||
105 | "MAILER@.*" | ||
106 | "MAILER-DAEMON@.*")) | ||
107 | |||
108 | (define (vacation-reply? msg aliases addresses days) | ||
109 | (let ((sender (mu-message-get-sender msg))) | ||
110 | (and | ||
111 | ;; No message will be sent unless an alias is part of either | ||
112 | ;; the "To:" or "Cc:" headers of the mail. | ||
113 | (call-with-current-continuation | ||
114 | (lambda (exit) | ||
115 | (for-each | ||
116 | (lambda (hdr) | ||
117 | (cond | ||
118 | (hdr | ||
119 | (let ((count (mu-address-get-count hdr))) | ||
120 | (do ((i 1 (1+ i))) | ||
121 | ((> i count) #f) | ||
122 | (let ((email (mu-address-get-email hdr i))) | ||
123 | (for-each | ||
124 | (lambda (alias) | ||
125 | (if (string-ci=? alias email) | ||
126 | (exit #t))) | ||
127 | aliases))))))) | ||
128 | (list (mu-message-get-header msg "To") | ||
129 | (mu-message-get-header msg "Cc"))) | ||
130 | #f)) | ||
131 | |||
132 | ;; Messages sent from one of the vacation-noreply-senders are not | ||
133 | ;; responded to | ||
134 | (call-with-current-continuation | ||
135 | (lambda (exit) | ||
136 | (do ((explist (append vacation-noreply-senders addresses) | ||
137 | (cdr explist))) | ||
138 | ((null? explist) #t) | ||
139 | (let ((rx (make-regexp (car explist) regexp/icase))) | ||
140 | (if (regexp-exec rx sender) | ||
141 | (exit #f)))))) | ||
142 | |||
143 | ;; Messages with Precedence: bulk or junk are not responded to | ||
144 | (let ((prec (mu-message-get-header msg "Precedence"))) | ||
145 | (not (and prec (or (string-ci=? prec "bulk") | ||
146 | (string-ci=? prec "junk"))))) | ||
147 | |||
148 | ;; Senders already in the database get no response | ||
149 | (not (vacation-db-lookup sender days))))) | ||
150 | |||
151 | (define (vacation-send-reply subject text sender) | ||
152 | (let ((sender "root@localhost") | ||
153 | (mesg (mu-message-create))) | ||
154 | (let ((port (mu-message-get-port mesg "w"))) | ||
155 | (display text port) | ||
156 | (close-output-port port)) | ||
157 | (mu-message-set-header mesg "X-Sender" | ||
158 | (string-append "vacation.scm, " mu-package-string) | ||
159 | #t) | ||
160 | (mu-message-send mesg #f #f sender))) | ||
161 | |||
162 | (define (action-vacation text . opt) | ||
163 | (sieve-verbose-print "VACATION") | ||
164 | (set! vacation-debug (member #:debug opt)) | ||
165 | (if vacation-debug | ||
166 | (begin | ||
167 | (display sieve-current-message)(display ": "))) | ||
168 | (cond | ||
169 | ((vacation-reply? sieve-current-message | ||
170 | (append (list sieve-my-email) | ||
171 | (sieve-get-opt-arg opt #:aliases '())) | ||
172 | (sieve-get-opt-arg opt #:addresses '()) | ||
173 | (sieve-get-opt-arg opt #:days #f)) | ||
174 | (vacation-send-reply (sieve-get-opt-arg | ||
175 | opt #:subject | ||
176 | (string-append "Re: " | ||
177 | (mu-message-get-header | ||
178 | sieve-current-message | ||
179 | "Subject"))) | ||
180 | text | ||
181 | (sieve-get-opt-arg | ||
182 | opt #:sender | ||
183 | (mu-message-get-sender sieve-current-message))) | ||
184 | (vacation-db-update sieve-current-message) | ||
185 | (if vacation-debug | ||
186 | (display "WILL REPLY\n"))) | ||
187 | (vacation-debug | ||
188 | (display "WILL NOT REPLY\n")))) | ||
189 | |||
190 | ;;; Register action | ||
191 | (if sieve-parser | ||
192 | (sieve-register-action "vacation" | ||
193 | action-vacation | ||
194 | (list 'string) | ||
195 | (list (cons "days" 'number) | ||
196 | (cons "addresses" 'string-list) | ||
197 | (cons "aliases" 'string-list) | ||
198 | (cons "subject" 'string) | ||
199 | (cons "sender" 'string) | ||
200 | (cons "mime" #f) | ||
201 | (cons "debug" #f)))) | ||
202 |
-
Please register or sign in to post a comment