1 /*@ S-nail - a mail user agent derived from Berkeley Mail.
2 *@ Client-side implementation of the IMAP SEARCH command. This is used
3 *@ for folders not located on IMAP servers, or for IMAP servers that do
4 *@ not implement the SEARCH command.
6 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
7 * Copyright (c) 2012 - 2014 Steffen "Daode" Nurpmeso <sdaoden@users.sf.net>.
11 * Gunnar Ritter. All rights reserved.
13 * Redistribution and use in source and binary forms, with or without
14 * modification, are permitted provided that the following conditions
16 * 1. Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
18 * 2. Redistributions in binary form must reproduce the above copyright
19 * notice, this list of conditions and the following disclaimer in the
20 * documentation and/or other materials provided with the distribution.
21 * 3. All advertising materials mentioning features or use of this software
22 * must display the following acknowledgement:
23 * This product includes software developed by Gunnar Ritter
24 * and his contributors.
25 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
26 * may be used to endorse or promote products derived from this software
27 * without specific prior written permission.
29 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``AS IS'' AND
30 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
31 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
32 * ARE DISCLAIMED. IN NO EVENT SHALL GUNNAR RITTER OR CONTRIBUTORS BE LIABLE
33 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
34 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
35 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
37 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
38 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
42 #ifndef HAVE_AMALGAMATION
51 ITBAD
, ITEOD
, ITBOL
, ITEOL
, ITAND
, ITSET
, ITALL
, ITANSWERED
,
52 ITBCC
, ITBEFORE
, ITBODY
,
62 ITSEEN
, ITSENTBEFORE
, ITSENTON
, ITSENTSINCE
, ITSINCE
, ITSMALLER
,
65 ITUID
, ITUNANSWERED
, ITUNDELETED
, ITUNDRAFT
, ITUNFLAGGED
, ITUNKEYWORD
,
76 struct itregex
*re_next
;
90 static struct itlex
const _it_strings
[] = {
92 { "ANSWERED", ITANSWERED
},
94 { "BEFORE", ITBEFORE
},
97 { "DELETED", ITDELETED
},
99 { "FLAGGED", ITFLAGGED
},
101 { "HEADER", ITHEADER
},
102 { "KEYWORD", ITKEYWORD
},
103 { "LARGER", ITLARGER
},
109 { "RECENT", ITRECENT
},
111 { "SENTBEFORE", ITSENTBEFORE
},
112 { "SENTON", ITSENTON
},
113 { "SENTSINCE", ITSENTSINCE
},
114 { "SINCE", ITSINCE
},
115 { "SMALLER", ITSMALLER
},
116 { "SUBJECT", ITSUBJECT
},
120 { "UNANSWERED", ITUNANSWERED
},
121 { "UNDELETED", ITUNDELETED
},
122 { "UNDRAFT", ITUNDRAFT
},
123 { "UNFLAGGED", ITUNFLAGGED
},
124 { "UNKEYWORD", ITUNKEYWORD
},
125 { "UNSEEN", ITUNSEEN
},
129 static struct itnode
*_it_tree
;
131 static struct itregex
*_it_regex
;
133 static char *_it_begin
;
134 static enum itoken _it_token
;
135 static unsigned long _it_number
;
136 static void *_it_args
[2];
137 static size_t _it_need_headers
;
138 static bool_t _it_need_regex
;
140 static enum okay
itparse(char const *spec
, char const **xp
, int sub
);
141 static enum okay
itscan(char const *spec
, char const **xp
);
142 static enum okay
itsplit(char const *spec
, char const **xp
);
143 static enum okay
itstring(void **tp
, char const *spec
, char const **xp
);
144 static int itexecute(struct mailbox
*mp
, struct message
*m
,
145 size_t c
, struct itnode
*n
);
146 static int matchfield(struct message
*m
, char const *field
,
148 static int matchenvelope(struct message
*m
, char const *field
,
150 static char * mkenvelope(struct name
*np
);
151 static int matchmsg(struct message
*m
, const void *what
,
153 static char const * around(char const *cp
);
156 itparse(char const *spec
, char const **xp
, int sub
)
159 struct itnode n
, *z
, *ittree
;
164 while ((rv
= itscan(spec
, xp
)) == OKAY
&& _it_token
!= ITBAD
&&
165 _it_token
!= ITEOD
) {
167 memset(&n
, 0, sizeof n
);
181 fprintf(stderr
, "Excess in \")\".\n");
189 if ((rv
= itparse(spec
, xp
, sub
+ 1)) == STOP
)
192 if ((n
.n_x
= _it_tree
) == NULL
) {
193 fprintf(stderr
, "Criterion for NOT missing: >>> %s <<<\n",
201 /* <search-key1> <search-key2> */
203 if ((rv
= itparse(spec
, xp
, sub
+ 1)) == STOP
)
205 if ((n
.n_x
= _it_tree
) == NULL
) {
206 fprintf(stderr
, "First criterion for OR missing: >>> %s <<<\n",
212 if ((rv
= itparse(spec
, xp
, sub
+ 1)) == STOP
)
215 if ((n
.n_y
= _it_tree
) == NULL
) {
216 fprintf(stderr
, "Second criterion for OR missing: >>> %s <<<\n",
223 n
.n_token
= _it_token
;
230 if (_it_tree
== NULL
) {
231 _it_tree
= salloc(sizeof *_it_tree
);
235 _it_tree
= salloc(sizeof *_it_tree
);
236 _it_tree
->n_token
= ITAND
;
238 _it_tree
->n_y
= salloc(sizeof *_it_tree
->n_y
);
241 if (sub
&& level
== 0)
250 itscan(char const *spec
, char const **xp
)
256 while (spacechar(*spec
))
268 while (spacechar(*spec
))
275 for (i
= 0; _it_strings
[i
].s_string
; i
++) {
276 n
= strlen(_it_strings
[i
].s_string
);
277 if (ascncasecmp(spec
, _it_strings
[i
].s_string
, n
) == 0 &&
278 (spacechar(spec
[n
]) || spec
[n
] == '\0' ||
279 spec
[n
] == '(' || spec
[n
] == ')')) {
280 _it_token
= _it_strings
[i
].s_token
;
282 while (spacechar(*spec
))
284 rv
= itsplit(spec
, xp
);
288 if (digitchar(*spec
)) {
289 _it_number
= strtoul(spec
, UNCONST(xp
), 10);
290 if (spacechar(**xp
) || **xp
== '\0' || **xp
== '(' || **xp
== ')') {
295 fprintf(stderr
, "Bad SEARCH criterion \"");
296 while (*spec
&& !spacechar(*spec
) && *spec
!= '(' && *spec
!= ')') {
297 putc(*spec
& 0377, stderr
);
300 fprintf(stderr
, "\": >>> %s <<<\n", around(*xp
));
309 itsplit(char const *spec
, char const **xp
)
326 rv
= itstring(&_it_args
[0], spec
, xp
);
328 if (rv
== OKAY
&& _it_need_regex
) {
343 if ((rv
= itstring(&_it_args
[0], spec
, xp
)) != OKAY
)
345 if ((t
= imap_read_date(_it_args
[0])) == (time_t)-1) {
346 fprintf(stderr
, "Invalid date \"%s\": >>> %s <<<\n",
347 (char*)_it_args
[0], around(*xp
));
355 /* <field-name> <string> */
357 if ((rv
= itstring(&_it_args
[0], spec
, xp
)) != OKAY
)
360 if ((rv
= itstring(&_it_args
[1], spec
, xp
)) != OKAY
)
365 if (_it_need_regex
) {
366 struct itregex
*itre
= salloc(sizeof *_it_regex
);
367 itre
->re_next
= _it_regex
;
370 cp
= _it_args
[_it_number
];
371 _it_args
[_it_number
] = &itre
->re_regex
;
372 if (regcomp(&itre
->re_regex
, cp
, REG_EXTENDED
| REG_ICASE
| REG_NOSUB
)
374 fprintf(stderr
, tr(526,
375 "Invalid regular expression \"%s\": >>> %s <<<\n"),
386 if ((rv
= itstring(&_it_args
[0], spec
, xp
)) != OKAY
)
388 if (asccasecmp(_it_args
[0], "\\Seen") == 0)
390 else if (asccasecmp(_it_args
[0], "\\Deleted") == 0)
391 _it_number
= MDELETED
;
392 else if (asccasecmp(_it_args
[0], "\\Recent") == 0)
394 else if (asccasecmp(_it_args
[0], "\\Flagged") == 0)
395 _it_number
= MFLAGGED
;
396 else if (asccasecmp(_it_args
[0], "\\Answered") == 0)
397 _it_number
= MANSWERED
;
398 else if (asccasecmp(_it_args
[0], "\\Draft") == 0)
406 if ((rv
= itstring(&_it_args
[0], spec
, xp
)) != OKAY
)
408 _it_number
= strtoul(_it_args
[0], &cp
, 10);
409 if (spacechar(*cp
) || *cp
== '\0')
411 fprintf(stderr
, "Invalid size: >>> %s <<<\n", around(*xp
));
417 "Searching for UIDs is not supported: >>> %s <<<\n", around(*xp
));
430 itstring(void **tp
, char const *spec
, char const **xp
)
437 while (spacechar(*spec
))
439 if (*spec
== '\0' || *spec
== '(' || *spec
== ')') {
440 fprintf(stderr
, "Missing string argument: >>> %s <<<\n",
441 around(&(*xp
)[spec
- *xp
]));
444 ap
= *tp
= salloc(strlen(spec
) + 1);
447 if (inquote
&& **xp
== '\\')
449 else if (**xp
== '"')
451 else if (!inquote
&& (spacechar(**xp
) || **xp
== '(' || **xp
== ')')) {
458 *tp
= imap_unquotestr(*tp
);
466 itexecute(struct mailbox
*mp
, struct message
*m
, size_t c
, struct itnode
*n
)
468 char *cp
, *line
= NULL
;
475 fprintf(stderr
, "Internal error: Empty node in SEARCH tree.\n");
480 switch (n
->n_token
) {
484 if (m
->m_time
== 0 && !(m
->m_flag
& MNOFROM
) &&
485 (ibuf
= setinput(mp
, m
, NEED_HEADER
)) != NULL
) {
486 if (readline_restart(ibuf
, &line
, &linesize
, 0) > 0)
487 m
->m_time
= unixtime(line
);
495 if ((cp
= hfield1("date", m
)) != NULL
)
496 m
->m_date
= rfctime(cp
);
502 switch (n
->n_token
) {
504 fprintf(stderr
, "Internal SEARCH error: Lost token %d\n", n
->n_token
);
508 rv
= itexecute(mp
, m
, c
, n
->n_x
) & itexecute(mp
, m
, c
, n
->n_y
);
511 rv
= UICMP(z
, c
, ==, n
->n_n
);
517 rv
= ((m
->m_flag
& MANSWERED
) != 0);
520 rv
= matchenvelope(m
, "bcc", n
->n_v
);
523 rv
= UICMP(z
, m
->m_time
, <, n
->n_n
);
526 rv
= matchmsg(m
, n
->n_v
, 0);
529 rv
= matchenvelope(m
, "cc", n
->n_v
);
532 rv
= ((m
->m_flag
& MDELETED
) != 0);
535 rv
= ((m
->m_flag
& MDRAFTED
) != 0);
538 rv
= ((m
->m_flag
& MFLAGGED
) != 0);
541 rv
= matchenvelope(m
, "from", n
->n_v
);
544 rv
= matchfield(m
, n
->n_v
, n
->n_w
);
547 rv
= ((m
->m_flag
& n
->n_n
) != 0);
550 rv
= (m
->m_xsize
> n
->n_n
);
553 rv
= ((m
->m_flag
& (MNEW
| MREAD
)) == MNEW
);
556 rv
= !itexecute(mp
, m
, c
, n
->n_x
);
559 rv
= !(m
->m_flag
& MNEW
);
562 rv
= (UICMP(z
, m
->m_time
, >=, n
->n_n
) &&
563 UICMP(z
, m
->m_time
, <, n
->n_n
+ 86400));
566 rv
= itexecute(mp
, m
, c
, n
->n_x
) | itexecute(mp
, m
, c
, n
->n_y
);
569 rv
= ((m
->m_flag
& MNEW
) != 0);
572 rv
= ((m
->m_flag
& MREAD
) != 0);
575 rv
= UICMP(z
, m
->m_date
, <, n
->n_n
);
578 rv
= (UICMP(z
, m
->m_date
, >=, n
->n_n
) &&
579 UICMP(z
, m
->m_date
, <, n
->n_n
+ 86400));
582 rv
= UICMP(z
, m
->m_date
, >=, n
->n_n
);
585 rv
= UICMP(z
, m
->m_time
, >=, n
->n_n
);
588 rv
= UICMP(z
, m
->m_xsize
, <, n
->n_n
);
591 rv
= matchfield(m
, "subject", n
->n_v
);
594 rv
= matchmsg(m
, n
->n_v
, 1);
597 rv
= matchenvelope(m
, "to", n
->n_v
);
600 rv
= !(m
->m_flag
& MANSWERED
);
603 rv
= !(m
->m_flag
& MDELETED
);
606 rv
= !(m
->m_flag
& MDRAFTED
);
609 rv
= !(m
->m_flag
& MFLAGGED
);
612 rv
= !(m
->m_flag
& n
->n_n
);
615 rv
= !(m
->m_flag
& MREAD
);
624 matchfield(struct message
*m
, char const *field
, const void *what
)
630 if ((in
.s
= hfieldX(field
, m
)) == NULL
)
634 mime_fromhdr(&in
, &out
, TD_ICONV
);
638 i
= (regexec(what
, out
.s
, 0,NULL
, 0) != REG_NOMATCH
);
641 i
= substr(out
.s
, what
);
650 matchenvelope(struct message
*m
, char const *field
, const void *what
)
657 if ((cp
= hfieldX(field
, m
)) == NULL
)
660 for (np
= lextract(cp
, GFULL
); np
!= NULL
; np
= np
->n_flink
) {
662 if (_it_need_regex
) {
663 if (regexec(what
, np
->n_name
, 0,NULL
, 0) == REG_NOMATCH
&&
664 regexec(what
, mkenvelope(np
), 0,NULL
, 0) == REG_NOMATCH
)
668 if (!substr(np
->n_name
, what
) && !substr(mkenvelope(np
), what
))
680 mkenvelope(struct name
*np
)
683 char *ep
, *realnam
= NULL
, *sourceaddr
= NULL
, *localpart
= NULL
,
684 *domainpart
= NULL
, *cp
, *rp
, *xp
, *ip
;
686 int level
= 0, hadphrase
= 0;
689 in
.s
= np
->n_fullname
;
691 mime_fromhdr(&in
, &out
, TD_ICONV
);
692 rp
= ip
= ac_alloc(strlen(out
.s
) + 1);
693 for (cp
= out
.s
; *cp
; cp
++) {
699 if (*cp
== '\\' && cp
[1])
705 while (cp
> out
.s
&& blankchar(cp
[-1]))
709 if (xp
< &cp
[-1] && *xp
== '"' && cp
[-1] == '"') {
720 if (hadphrase
++ == 0)
741 localpart
= savestr(np
->n_name
);
742 if ((cp
= strrchr(localpart
, '@')) != NULL
) {
747 ep
= salloc(epsize
= strlen(np
->n_fullname
) * 2 + 40);
748 snprintf(ep
, epsize
, "(%s %s %s %s)",
749 realnam
? imap_quotestr(realnam
) : "NIL",
750 sourceaddr
? imap_quotestr(sourceaddr
) : "NIL",
751 localpart
? imap_quotestr(localpart
) : "NIL",
752 domainpart
? imap_quotestr(domainpart
) : "NIL");
759 matchmsg(struct message
*m
, const void *what
, int withheader
)
762 size_t linesize
, linelen
, cnt
;
767 if ((fp
= Ftmp(NULL
, "imasrch", OF_RDWR
| OF_UNLINK
| OF_REGISTER
, 0600)) ==
770 if (sendmp(m
, fp
, NULL
, NULL
, SEND_TOSRCH
, NULL
) < 0)
776 line
= smalloc(linesize
= LINESIZE
);
780 while (fgetline(&line
, &linesize
, &cnt
, &linelen
, fp
, 0))
784 while (fgetline(&line
, &linesize
, &cnt
, &linelen
, fp
, 0)) {
786 if (_it_need_regex
) {
787 if (regexec(what
, line
, 0,NULL
, 0) == REG_NOMATCH
)
791 if (!substr(line
, what
))
805 #define SURROUNDING 16
807 around(char const *cp
)
809 static char ab
[2 * SURROUNDING
+1];
814 for (i
= 0; i
< SURROUNDING
&& cp
> _it_begin
; ++i
)
816 for (i
= 0; i
< sizeof(ab
) - 1; ++i
)
824 imap_search(char const *spec
, int f
)
826 static char *lastspec
;
833 if (strcmp(spec
, "()")) {
834 if (lastspec
!= NULL
)
836 _it_need_regex
= (spec
[0] == '(' && spec
[1] == '/');
838 lastspec
= sbufdup(spec
+ _it_need_regex
, i
- _it_need_regex
);
841 } else if (lastspec
== NULL
) {
842 fprintf(stderr
, tr(524, "No last SEARCH criteria available.\n"));
846 _it_begin
= lastspec
;
848 /* Regular expression searches are always local */
849 _it_need_headers
= FAL0
;
850 if (!_it_need_regex
) {
852 if ((rv
= imap_search1(spec
, f
) == OKAY
))
858 fprintf(stderr
, tr(525, "No regular expression support for SEARCHes.\n"));
863 if (itparse(spec
, &xp
, 0) == STOP
)
865 if (_it_tree
== NULL
) {
871 if (mb
.mb_type
== MB_IMAP
&& _it_need_headers
)
872 imap_getheaders(1, msgCount
);
875 for (i
= 0; UICMP(z
, i
, <, msgCount
); ++i
) {
876 if (message
[i
].m_flag
& MHIDDEN
)
878 if (f
== MDELETED
|| !(message
[i
].m_flag
& MDELETED
)) {
879 size_t j
= (int)(i
+ 1);
880 if (itexecute(&mb
, &message
[i
], j
, _it_tree
))
890 for (; _it_regex
!= NULL
; _it_regex
= _it_regex
->re_next
)
891 regfree(&_it_regex
->re_regex
);
898 /* vim:set fenc=utf-8:s-it-mode */