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 - 2018 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
8 * SPDX-License-Identifier: BSD-4-Clause
12 * Gunnar Ritter. All rights reserved.
14 * Redistribution and use in source and binary forms, with or without
15 * modification, are permitted provided that the following conditions
17 * 1. Redistributions of source code must retain the above copyright
18 * notice, this list of conditions and the following disclaimer.
19 * 2. Redistributions in binary form must reproduce the above copyright
20 * notice, this list of conditions and the following disclaimer in the
21 * documentation and/or other materials provided with the distribution.
22 * 3. All advertising materials mentioning features or use of this software
23 * must display the following acknowledgement:
24 * This product includes software developed by Gunnar Ritter
25 * and his contributors.
26 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
27 * may be used to endorse or promote products derived from this software
28 * without specific prior written permission.
30 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``AS IS'' AND
31 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
32 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33 * ARE DISCLAIMED. IN NO EVENT SHALL GUNNAR RITTER OR CONTRIBUTORS BE LIABLE
34 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
35 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
36 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
37 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
38 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
39 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
43 #define n_FILE imap_search
45 #ifndef HAVE_AMALGAMATION
50 #ifdef HAVE_IMAP_SEARCH
53 ITBAD
, ITEOD
, ITBOL
, ITEOL
, ITAND
, ITSET
, ITALL
, ITANSWERED
,
54 ITBCC
, ITBEFORE
, ITBODY
,
64 ITSEEN
, ITSENTBEFORE
, ITSENTON
, ITSENTSINCE
, ITSINCE
, ITSMALLER
,
67 ITUID
, ITUNANSWERED
, ITUNDELETED
, ITUNDRAFT
, ITUNFLAGGED
, ITUNKEYWORD
,
85 static struct itlex
const _it_strings
[] = {
87 { "ANSWERED", ITANSWERED
},
89 { "BEFORE", ITBEFORE
},
92 { "DELETED", ITDELETED
},
94 { "FLAGGED", ITFLAGGED
},
96 { "HEADER", ITHEADER
},
97 { "KEYWORD", ITKEYWORD
},
98 { "LARGER", ITLARGER
},
104 { "RECENT", ITRECENT
},
106 { "SENTBEFORE", ITSENTBEFORE
},
107 { "SENTON", ITSENTON
},
108 { "SENTSINCE", ITSENTSINCE
},
109 { "SINCE", ITSINCE
},
110 { "SMALLER", ITSMALLER
},
111 { "SUBJECT", ITSUBJECT
},
115 { "UNANSWERED", ITUNANSWERED
},
116 { "UNDELETED", ITUNDELETED
},
117 { "UNDRAFT", ITUNDRAFT
},
118 { "UNFLAGGED", ITUNFLAGGED
},
119 { "UNKEYWORD", ITUNKEYWORD
},
120 { "UNSEEN", ITUNSEEN
},
124 static struct itnode
*_it_tree
;
125 static char *_it_begin
;
126 static enum itoken _it_token
;
127 static uiz_t _it_number
;
128 static void *_it_args
[2];
129 static size_t _it_need_headers
;
131 static enum okay
itparse(char const *spec
, char const **xp
, int sub
);
132 static enum okay
itscan(char const *spec
, char const **xp
);
133 static enum okay
itsplit(char const *spec
, char const **xp
);
134 static enum okay
itstring(void **tp
, char const *spec
, char const **xp
);
135 static int itexecute(struct mailbox
*mp
, struct message
*m
,
136 size_t c
, struct itnode
*n
);
138 static time_t _imap_read_date(char const *cp
);
139 static char * _imap_quotestr(char const *s
);
140 static char * _imap_unquotestr(char const *s
);
142 static bool_t
matchfield(struct message
*m
, char const *field
,
144 static int matchenvelope(struct message
*m
, char const *field
,
146 static char * mkenvelope(struct name
*np
);
147 static char const * around(char const *cp
);
150 itparse(char const *spec
, char const **xp
, int sub
)
153 struct itnode n
, *z
, *ittree
;
158 while ((rv
= itscan(spec
, xp
)) == OKAY
&& _it_token
!= ITBAD
&&
159 _it_token
!= ITEOD
) {
161 memset(&n
, 0, sizeof n
);
175 n_err(_("Excess in )\n"));
183 if ((rv
= itparse(spec
, xp
, sub
+ 1)) == STOP
)
186 if ((n
.n_x
= _it_tree
) == NULL
) {
187 n_err(_("Criterion for NOT missing: >>> %s <<<\n"), around(*xp
));
194 /* <search-key1> <search-key2> */
196 if ((rv
= itparse(spec
, xp
, sub
+ 1)) == STOP
)
198 if ((n
.n_x
= _it_tree
) == NULL
) {
199 n_err(_("First criterion for OR missing: >>> %s <<<\n"),
205 if ((rv
= itparse(spec
, xp
, sub
+ 1)) == STOP
)
208 if ((n
.n_y
= _it_tree
) == NULL
) {
209 n_err(_("Second criterion for OR missing: >>> %s <<<\n"),
216 n
.n_token
= _it_token
;
223 if (_it_tree
== NULL
) {
224 _it_tree
= n_autorec_alloc(sizeof *_it_tree
);
228 _it_tree
= n_autorec_alloc(sizeof *_it_tree
);
229 _it_tree
->n_token
= ITAND
;
231 _it_tree
->n_y
= n_autorec_alloc(sizeof *_it_tree
->n_y
);
234 if (sub
&& level
== 0)
243 itscan(char const *spec
, char const **xp
)
249 while (spacechar(*spec
))
261 while (spacechar(*spec
))
268 #define __GO(C) ((C) != '\0' && (C) != '(' && (C) != ')' && !spacechar(C))
269 for (i
= 0; _it_strings
[i
].s_string
!= NULL
; ++i
) {
270 n
= strlen(_it_strings
[i
].s_string
);
271 if (!ascncasecmp(spec
, _it_strings
[i
].s_string
, n
) && !__GO(spec
[n
])) {
272 _it_token
= _it_strings
[i
].s_token
;
274 while (spacechar(*spec
))
276 rv
= itsplit(spec
, xp
);
280 if (digitchar(*spec
)) {
281 n_idec_uiz_cp(&_it_number
, spec
, 10, xp
);
288 n_err(_("Bad SEARCH criterion: "));
289 for (i
= 0; __GO(spec
[i
]); ++i
)
291 n_err(_("%.*s: >>> %s <<<\n"), i
, spec
, around(*xp
));
302 itsplit(char const *spec
, char const **xp
)
319 rv
= itstring(_it_args
, spec
, xp
);
330 if ((rv
= itstring(_it_args
, spec
, xp
)) != OKAY
)
332 if ((t
= _imap_read_date(_it_args
[0])) == (time_t)-1) {
333 n_err(_("Invalid date %s: >>> %s <<<\n"),
334 (char*)_it_args
[0], around(*xp
));
342 /* <field-name> <string> */
344 if ((rv
= itstring(_it_args
, spec
, xp
)) != OKAY
)
347 if ((rv
= itstring(_it_args
+ 1, spec
, xp
)) != OKAY
)
352 /* <flag> */ /* TODO use table->flag map search instead */
353 if ((rv
= itstring(_it_args
, spec
, xp
)) != OKAY
)
355 if (!asccasecmp(_it_args
[0], "\\Seen"))
357 else if (!asccasecmp(_it_args
[0], "\\Deleted"))
358 _it_number
= MDELETED
;
359 else if (!asccasecmp(_it_args
[0], "\\Recent"))
361 else if (!asccasecmp(_it_args
[0], "\\Flagged"))
362 _it_number
= MFLAGGED
;
363 else if (!asccasecmp(_it_args
[0], "\\Answered"))
364 _it_number
= MANSWERED
;
365 else if (!asccasecmp(_it_args
[0], "\\Draft"))
373 if ((rv
= itstring(_it_args
, spec
, xp
)) != OKAY
)
376 n_idec_uiz_cp(&_it_number
, _it_args
[0], 10, &cp
);
378 if (spacechar(*cp
) || *cp
== '\0')
380 n_err(_("Invalid size: >>> %s <<<\n"), around(*xp
));
385 n_err(_("Searching for UIDs is not supported: >>> %s <<<\n"),
399 itstring(void **tp
, char const *spec
, char const **xp
) /* XXX lesser derefs */
406 while (spacechar(*spec
))
408 if (*spec
== '\0' || *spec
== '(' || *spec
== ')') {
409 n_err(_("Missing string argument: >>> %s <<<\n"),
410 around(&(*xp
)[spec
- *xp
]));
413 ap
= *tp
= n_autorec_alloc(strlen(spec
) +1);
416 if (inquote
&& **xp
== '\\')
418 else if (**xp
== '"')
420 else if (!inquote
&& (spacechar(**xp
) || **xp
== '(' || **xp
== ')')) {
427 *tp
= _imap_unquotestr(*tp
);
435 itexecute(struct mailbox
*mp
, struct message
*m
, size_t c
, struct itnode
*n
)
437 struct search_expr se
;
438 char *cp
, *line
= NULL
; /* TODO line pool */
445 n_err(_("Internal error: Empty node in SEARCH tree\n"));
450 switch (n
->n_token
) {
454 if (m
->m_time
== 0 && !(m
->m_flag
& MNOFROM
) &&
455 (ibuf
= setinput(mp
, m
, NEED_HEADER
)) != NULL
) {
456 if (readline_restart(ibuf
, &line
, &linesize
, 0) > 0)
457 m
->m_time
= unixtime(line
);
465 if ((cp
= hfield1("date", m
)) != NULL
)
466 m
->m_date
= rfctime(cp
);
472 switch (n
->n_token
) {
474 n_err(_("Internal SEARCH error: Lost token %d\n"), n
->n_token
);
478 rv
= itexecute(mp
, m
, c
, n
->n_x
) & itexecute(mp
, m
, c
, n
->n_y
);
487 rv
= ((m
->m_flag
& MANSWERED
) != 0);
490 rv
= matchenvelope(m
, "bcc", n
->n_v
);
493 rv
= UICMP(z
, m
->m_time
, <, n
->n_n
);
496 memset(&se
, 0, sizeof se
);
497 se
.ss_field
= "body";
499 rv
= message_match(m
, &se
, FAL0
);
502 rv
= matchenvelope(m
, "cc", n
->n_v
);
505 rv
= ((m
->m_flag
& MDELETED
) != 0);
508 rv
= ((m
->m_flag
& MDRAFTED
) != 0);
511 rv
= ((m
->m_flag
& MFLAGGED
) != 0);
514 rv
= matchenvelope(m
, "from", n
->n_v
);
517 rv
= matchfield(m
, n
->n_v
, n
->n_w
);
520 rv
= ((m
->m_flag
& n
->n_n
) != 0);
523 rv
= (m
->m_xsize
> n
->n_n
);
526 rv
= ((m
->m_flag
& (MNEW
| MREAD
)) == MNEW
);
529 rv
= !itexecute(mp
, m
, c
, n
->n_x
);
532 rv
= !(m
->m_flag
& MNEW
);
535 rv
= (UICMP(z
, m
->m_time
, >=, n
->n_n
) &&
536 UICMP(z
, m
->m_time
, <, n
->n_n
+ 86400));
539 rv
= itexecute(mp
, m
, c
, n
->n_x
) | itexecute(mp
, m
, c
, n
->n_y
);
542 rv
= ((m
->m_flag
& MNEW
) != 0);
545 rv
= ((m
->m_flag
& MREAD
) != 0);
548 rv
= UICMP(z
, m
->m_date
, <, n
->n_n
);
551 rv
= (UICMP(z
, m
->m_date
, >=, n
->n_n
) &&
552 UICMP(z
, m
->m_date
, <, n
->n_n
+ 86400));
555 rv
= UICMP(z
, m
->m_date
, >=, n
->n_n
);
558 rv
= UICMP(z
, m
->m_time
, >=, n
->n_n
);
561 rv
= UICMP(z
, m
->m_xsize
, <, n
->n_n
);
564 rv
= matchfield(m
, "subject", n
->n_v
);
567 memset(&se
, 0, sizeof se
);
568 se
.ss_field
= "text";
570 rv
= message_match(m
, &se
, TRU1
);
573 rv
= matchenvelope(m
, "to", n
->n_v
);
576 rv
= !(m
->m_flag
& MANSWERED
);
579 rv
= !(m
->m_flag
& MDELETED
);
582 rv
= !(m
->m_flag
& MDRAFTED
);
585 rv
= !(m
->m_flag
& MFLAGGED
);
588 rv
= !(m
->m_flag
& n
->n_n
);
591 rv
= !(m
->m_flag
& MREAD
);
600 _imap_read_date(char const *cp
)
603 si32_t year
, month
, day
, i
, tzdiff
;
610 n_idec_si32_cp(&day
, cp
, 10, &xp
);
611 if (day
<= 0 || day
> 31 || *xp
++ != '-')
615 if (!ascncasecmp(xp
, n_month_names
[i
], 3))
617 if (n_month_names
[++i
][0] == '\0')
623 n_idec_si32_cp(&year
, &xp
[4], 10, &yp
);
624 if (year
< 1970 || year
> 2037 || PTRCMP(yp
, !=, xp
+ 8))
626 if (yp
[0] != '\0' && (yp
[1] != '"' || yp
[2] != '\0'))
628 if ((t
= combinetime(year
, month
, day
, 0, 0, 0)) == (time_t)-1)
630 if((t2
= mktime(gmtime(&t
))) == (time_t)-1)
633 if((tmptr
= localtime(&t
)) == NULL
)
635 if (tmptr
->tm_isdst
> 0)
647 _imap_quotestr(char const *s
)
652 np
= n
= n_autorec_alloc(2 * strlen(s
) + 3);
655 if (*s
== '"' || *s
== '\\')
666 _imap_unquotestr(char const *s
)
676 np
= n
= n_autorec_alloc(strlen(s
) + 1);
691 matchfield(struct message
*m
, char const *field
, void const *what
)
697 if ((in
.s
= hfieldX(field
, m
)) == NULL
)
701 mime_fromhdr(&in
, &out
, TD_ICONV
);
702 rv
= substr(out
.s
, what
);
710 matchenvelope(struct message
*m
, char const *field
, void const *what
)
717 if ((cp
= hfieldX(field
, m
)) == NULL
)
720 for (np
= lextract(cp
, GFULL
); np
!= NULL
; np
= np
->n_flink
) {
721 if (!substr(np
->n_name
, what
) && !substr(mkenvelope(np
), what
))
733 mkenvelope(struct name
*np
)
736 char *ep
, *realnam
= NULL
, /**sourceaddr = NULL,*/ *localpart
,
737 *domainpart
, *cp
, *rp
, *xp
, *ip
;
740 bool_t hadphrase
= FAL0
;
743 in
.s
= np
->n_fullname
;
745 mime_fromhdr(&in
, &out
, TD_ICONV
);
746 rp
= ip
= n_lofi_alloc(strlen(out
.s
) + 1);
747 for (cp
= out
.s
; *cp
; cp
++) {
753 if (cp
[0] == '\\' && cp
[1] != '\0')
759 while (cp
> out
.s
&& blankchar(cp
[-1]))
763 if (PTRCMP(xp
, <, cp
- 1) && *xp
== '"' && cp
[-1] == '"') {
783 if (level
&& cp
[1] != '\0')
796 localpart
= savestr(np
->n_name
);
797 if ((cp
= strrchr(localpart
, '@')) != NULL
) {
803 ep
= n_autorec_alloc(epsize
= strlen(np
->n_fullname
) * 2 + 40);
804 snprintf(ep
, epsize
, "(%s %s %s %s)",
805 realnam
? _imap_quotestr(realnam
) : "NIL",
806 /*sourceaddr ? _imap_quotestr(sourceaddr) :*/ "NIL",
807 _imap_quotestr(localpart
),
808 domainpart
? _imap_quotestr(domainpart
) : "NIL");
814 #define SURROUNDING 16
816 around(char const *cp
)
818 static char ab
[2 * SURROUNDING
+1];
823 for (i
= 0; i
< SURROUNDING
&& cp
> _it_begin
; ++i
)
825 for (i
= 0; i
< sizeof(ab
) -1; ++i
)
833 imap_search(char const *spec
, int f
)
835 static char *lastspec
;
842 if (strcmp(spec
, "()")) {
843 if (lastspec
!= NULL
)
846 lastspec
= sbufdup(spec
, i
);
847 } else if (lastspec
== NULL
) {
848 n_err(_("No last SEARCH criteria available\n"));
853 _it_begin
= lastspec
;
855 _it_need_headers
= FAL0
;
857 if ((rv
= imap_search1(spec
, f
) == OKAY
))
860 if (itparse(spec
, &xp
, 0) == STOP
){
867 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
)){
892 #endif /* HAVE_IMAP_SEARCH */