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 - 2013 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
, const char *field
, const void *what
);
147 static int matchenvelope(struct message
*m
, const char *field
,
149 static char *mkenvelope(struct name
*np
);
150 static int matchmsg(struct message
*m
, const void *what
, int withheader
);
151 static const char *around(const char *cp
);
154 imap_search(const char *spec
, int f
)
156 static char *lastspec
;
162 if (strcmp(spec
, "()")) {
163 if (lastspec
!= NULL
)
165 _it_need_regex
= (spec
[0] == '(' && spec
[1] == '/');
167 lastspec
= sbufdup(spec
+ _it_need_regex
, i
- _it_need_regex
);
170 } else if (lastspec
== NULL
) {
171 fprintf(stderr
, tr(524, "No last SEARCH criteria available.\n"));
175 _it_begin
= lastspec
;
177 /* Regular expression searches are always local */
178 _it_need_headers
= FAL0
;
179 if (!_it_need_regex
) {
181 if ((rv
= imap_search1(spec
, f
) == OKAY
))
187 fprintf(stderr
, tr(525, "No regular expression support for SEARCHes.\n"));
192 if (itparse(spec
, &xp
, 0) == STOP
)
194 if (_it_tree
== NULL
) {
200 if (mb
.mb_type
== MB_IMAP
&& _it_need_headers
)
201 imap_getheaders(1, msgCount
);
204 for (i
= 0; UICMP(z
, i
, <, msgCount
); ++i
) {
205 if (message
[i
].m_flag
& MHIDDEN
)
207 if (f
== MDELETED
|| !(message
[i
].m_flag
& MDELETED
)) {
208 size_t j
= (int)(i
+ 1);
209 if (itexecute(&mb
, &message
[i
], j
, _it_tree
))
219 for (; _it_regex
!= NULL
; _it_regex
= _it_regex
->re_next
)
220 regfree(&_it_regex
->re_regex
);
227 itparse(char const *spec
, char const **xp
, int sub
)
230 struct itnode n
, *z
, *ittree
;
234 while ((ok
= itscan(spec
, xp
)) == OKAY
&& _it_token
!= ITBAD
&&
235 _it_token
!= ITEOD
) {
237 memset(&n
, 0, sizeof n
);
252 fprintf(stderr
, "Excess in \")\".\n");
259 if (itparse(spec
, xp
, sub
+1) == STOP
)
262 if ((n
.n_x
= _it_tree
) == NULL
) {
264 "Criterion for NOT missing: >>> %s <<<\n",
271 /* <search-key1> <search-key2> */
273 if (itparse(spec
, xp
, sub
+1) == STOP
)
275 if ((n
.n_x
= _it_tree
) == NULL
) {
276 fprintf(stderr
, "First criterion for OR "
277 "missing: >>> %s <<<\n",
282 if (itparse(spec
, xp
, sub
+1) == STOP
)
285 if ((n
.n_y
= _it_tree
) == NULL
) {
286 fprintf(stderr
, "Second criterion for OR "
287 "missing: >>> %s <<<\n",
293 n
.n_token
= _it_token
;
299 if (_it_tree
== NULL
) {
300 _it_tree
= salloc(sizeof *_it_tree
);
304 _it_tree
= salloc(sizeof *_it_tree
);
305 _it_tree
->n_token
= ITAND
;
307 _it_tree
->n_y
= salloc(sizeof *_it_tree
->n_y
);
310 if (sub
&& level
== 0)
317 itscan(char const *spec
, char const **xp
)
321 while (spacechar(*spec
))
333 while (spacechar(*spec
))
339 for (i
= 0; _it_strings
[i
].s_string
; i
++) {
340 n
= strlen(_it_strings
[i
].s_string
);
341 if (ascncasecmp(spec
, _it_strings
[i
].s_string
, n
) == 0 &&
342 (spacechar(spec
[n
]) || spec
[n
] == '\0' ||
343 spec
[n
] == '(' || spec
[n
] == ')')) {
344 _it_token
= _it_strings
[i
].s_token
;
346 while (spacechar(*spec
))
348 return itsplit(spec
, xp
);
351 if (digitchar(*spec
)) {
352 _it_number
= strtoul(spec
, UNCONST(xp
), 10);
353 if (spacechar(**xp
) || **xp
== '\0' ||
354 **xp
== '(' || **xp
== ')') {
359 fprintf(stderr
, "Bad SEARCH criterion \"");
360 while (*spec
&& !spacechar(*spec
) && *spec
!= '(' && *spec
!= ')') {
361 putc(*spec
&0377, stderr
);
364 fprintf(stderr
, "\": >>> %s <<<\n", around(*xp
));
370 itsplit(char const *spec
, char const **xp
)
386 rv
= itstring(&_it_args
[0], spec
, xp
);
388 if (rv
== OKAY
&& _it_need_regex
) {
403 if ((rv
= itstring(&_it_args
[0], spec
, xp
)) != OKAY
)
405 if ((t
= imap_read_date(_it_args
[0])) == (time_t)-1) {
406 fprintf(stderr
, "Invalid date \"%s\": >>> %s <<<\n",
407 (char*)_it_args
[0], around(*xp
));
415 /* <field-name> <string> */
417 if ((rv
= itstring(&_it_args
[0], spec
, xp
)) != OKAY
)
420 if ((rv
= itstring(&_it_args
[1], spec
, xp
)) != OKAY
)
425 if (_it_need_regex
) {
426 struct itregex
*itre
= salloc(sizeof *_it_regex
);
427 itre
->re_next
= _it_regex
;
430 cp
= _it_args
[_it_number
];
431 _it_args
[_it_number
] = &itre
->re_regex
;
432 if (regcomp(&itre
->re_regex
, cp
, REG_EXTENDED
| REG_ICASE
| REG_NOSUB
)
434 fprintf(stderr
, tr(526,
435 "Invalid regular expression \"%s\": >>> %s <<<\n"),
446 if ((rv
= itstring(&_it_args
[0], spec
, xp
)) != OKAY
)
448 if (asccasecmp(_it_args
[0], "\\Seen") == 0)
450 else if (asccasecmp(_it_args
[0], "\\Deleted") == 0)
451 _it_number
= MDELETED
;
452 else if (asccasecmp(_it_args
[0], "\\Recent") == 0)
454 else if (asccasecmp(_it_args
[0], "\\Flagged") == 0)
455 _it_number
= MFLAGGED
;
456 else if (asccasecmp(_it_args
[0], "\\Answered") == 0)
457 _it_number
= MANSWERED
;
458 else if (asccasecmp(_it_args
[0], "\\Draft") == 0)
466 if ((rv
= itstring(&_it_args
[0], spec
, xp
)) != OKAY
)
468 _it_number
= strtoul(_it_args
[0], &cp
, 10);
469 if (spacechar(*cp
) || *cp
== '\0')
471 fprintf(stderr
, "Invalid size: >>> %s <<<\n",
478 "Searching for UIDs is not supported: >>> %s <<<\n",
491 itstring(void **tp
, char const *spec
, char const **xp
)
496 while (spacechar(*spec
))
498 if (*spec
== '\0' || *spec
== '(' || *spec
== ')') {
499 fprintf(stderr
, "Missing string argument: >>> %s <<<\n",
500 around(&(*xp
)[spec
- *xp
]));
503 ap
= *tp
= salloc(strlen(spec
) + 1);
506 if (inquote
&& **xp
== '\\')
508 else if (**xp
== '"')
510 else if (!inquote
&& (spacechar(**xp
) ||
511 **xp
== '(' || **xp
== ')')) {
518 *tp
= imap_unquotestr(*tp
);
523 itexecute(struct mailbox
*mp
, struct message
*m
, size_t c
, struct itnode
*n
)
525 char *cp
, *line
= NULL
;
530 fprintf(stderr
, "Internal error: Empty node in SEARCH tree.\n");
533 switch (n
->n_token
) {
537 if (m
->m_time
== 0 && (m
->m_flag
&MNOFROM
) == 0 &&
538 (ibuf
= setinput(mp
, m
, NEED_HEADER
)) != NULL
) {
539 if (readline_restart(ibuf
, &line
, &linesize
, 0) > 0)
540 m
->m_time
= unixtime(line
);
548 if ((cp
= hfield1("date", m
)) != NULL
)
549 m
->m_date
= rfctime(cp
);
554 switch (n
->n_token
) {
556 fprintf(stderr
, "Internal SEARCH error: Lost token %d\n",
560 return itexecute(mp
, m
, c
, n
->n_x
) &
561 itexecute(mp
, m
, c
, n
->n_y
);
563 return (unsigned long)c
== n
->n_n
;
567 return (m
->m_flag
&MANSWERED
) != 0;
569 return matchenvelope(m
, "bcc", n
->n_v
);
571 return (unsigned long)m
->m_time
< n
->n_n
;
573 return matchmsg(m
, n
->n_v
, 0);
575 return matchenvelope(m
, "cc", n
->n_v
);
577 return (m
->m_flag
&MDELETED
) != 0;
579 return (m
->m_flag
&MDRAFTED
) != 0;
581 return (m
->m_flag
&MFLAGGED
) != 0;
583 return matchenvelope(m
, "from", n
->n_v
);
585 return matchfield(m
, n
->n_v
, n
->n_w
);
587 return (m
->m_flag
& n
->n_n
) != 0;
589 return m
->m_xsize
> n
->n_n
;
591 return (m
->m_flag
&(MNEW
|MREAD
)) == MNEW
;
593 return !itexecute(mp
, m
, c
, n
->n_x
);
595 return (m
->m_flag
&MNEW
) == 0;
597 return ((unsigned long)m
->m_time
>= n
->n_n
&&
598 (unsigned long)m
->m_time
< n
->n_n
+ 86400);
600 return itexecute(mp
, m
, c
, n
->n_x
) |
601 itexecute(mp
, m
, c
, n
->n_y
);
603 return (m
->m_flag
&MNEW
) != 0;
605 return (m
->m_flag
&MREAD
) != 0;
607 return (unsigned long)m
->m_date
< n
->n_n
;
609 return ((unsigned long)m
->m_date
>= n
->n_n
&&
610 (unsigned long)m
->m_date
< n
->n_n
+ 86400);
612 return (unsigned long)m
->m_date
>= n
->n_n
;
614 return (unsigned long)m
->m_time
>= n
->n_n
;
616 return (unsigned long)m
->m_xsize
< n
->n_n
;
618 return matchfield(m
, "subject", n
->n_v
);
620 return matchmsg(m
, n
->n_v
, 1);
622 return matchenvelope(m
, "to", n
->n_v
);
624 return (m
->m_flag
&MANSWERED
) == 0;
626 return (m
->m_flag
&MDELETED
) == 0;
628 return (m
->m_flag
&MDRAFTED
) == 0;
630 return (m
->m_flag
&MFLAGGED
) == 0;
632 return (m
->m_flag
& n
->n_n
) == 0;
634 return (m
->m_flag
&MREAD
) == 0;
639 matchfield(struct message
*m
, const char *field
, const void *what
)
644 if ((in
.s
= hfieldX(field
, m
)) == NULL
)
648 mime_fromhdr(&in
, &out
, TD_ICONV
);
652 i
= (regexec(what
, out
.s
, 0,NULL
, 0) != REG_NOMATCH
);
655 i
= substr(out
.s
, what
);
663 matchenvelope(struct message
*m
, const char *field
, const void *what
)
669 if ((cp
= hfieldX(field
, m
)) == NULL
)
672 for (np
= lextract(cp
, GFULL
); np
!= NULL
; np
= np
->n_flink
) {
674 if (_it_need_regex
) {
675 if (regexec(what
, np
->n_name
, 0,NULL
, 0) == REG_NOMATCH
&&
676 regexec(what
, mkenvelope(np
), 0,NULL
, 0) == REG_NOMATCH
)
680 if (!substr(np
->n_name
, what
) && !substr(mkenvelope(np
), what
))
691 mkenvelope(struct name
*np
)
695 char *realnam
= NULL
, *sourceaddr
= NULL
,
696 *localpart
= NULL
, *domainpart
= NULL
,
699 int level
= 0, hadphrase
= 0;
701 in
.s
= np
->n_fullname
;
703 mime_fromhdr(&in
, &out
, TD_ICONV
);
704 rp
= ip
= ac_alloc(strlen(out
.s
) + 1);
705 for (cp
= out
.s
; *cp
; cp
++) {
711 if (*cp
== '\\' && cp
[1])
717 while (cp
> out
.s
&& blankchar(cp
[-1]&0377))
721 if (xp
< &cp
[-1] && *xp
== '"' && cp
[-1] == '"') {
732 if (hadphrase
++ == 0)
752 localpart
= savestr(np
->n_name
);
753 if ((cp
= strrchr(localpart
, '@')) != NULL
) {
757 ep
= salloc(epsize
= strlen(np
->n_fullname
) * 2 + 40);
758 snprintf(ep
, epsize
, "(%s %s %s %s)",
759 realnam
? imap_quotestr(realnam
) : "NIL",
760 sourceaddr
? imap_quotestr(sourceaddr
) : "NIL",
761 localpart
? imap_quotestr(localpart
) : "NIL",
762 domainpart
? imap_quotestr(domainpart
) : "NIL");
768 matchmsg(struct message
*m
, const void *what
, int withheader
)
770 char *tempFile
, *line
= NULL
;
771 size_t linesize
, linelen
, cnt
;
775 if ((fp
= Ftemp(&tempFile
, "Ra", "w+", 0600, 1)) == NULL
)
779 if (sendmp(m
, fp
, NULL
, NULL
, SEND_TOSRCH
, NULL
) < 0)
785 line
= smalloc(linesize
= LINESIZE
);
789 while (fgetline(&line
, &linesize
, &cnt
, &linelen
, fp
, 0))
793 while (fgetline(&line
, &linesize
, &cnt
, &linelen
, fp
, 0)) {
795 if (_it_need_regex
) {
796 if (regexec(what
, line
, 0,NULL
, 0) == REG_NOMATCH
)
800 if (!substr(line
, what
))
813 #define SURROUNDING 16
815 around(const char *cp
)
817 static char ab
[2 * SURROUNDING
+1];
821 for (i
= 0; i
< SURROUNDING
&& cp
> _it_begin
; ++i
)
823 for (i
= 0; i
< sizeof(ab
) - 1; ++i
)
829 /* vim:set fenc=utf-8:s-it-mode (TODO only partial true) */