Do not use non-compiler-builtin alloca(3)s..
[s-mailx.git] / imap_search.c
bloba4fbc47835f9121446c08bf6fd5587ec399b4f0c
1 /*
2 * S-nail - a mail user agent derived from Berkeley Mail.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 Steffen "Daode" Nurpmeso.
6 */
7 /*
8 * Copyright (c) 2004
9 * Gunnar Ritter. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
13 * are met:
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by Gunnar Ritter
22 * and his contributors.
23 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL GUNNAR RITTER OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
37 * SUCH DAMAGE.
40 #include "config.h"
42 #include "rcv.h"
43 #include "extern.h"
44 #include <time.h>
47 * Mail -- a mail program
49 * Client-side implementation of the IMAP SEARCH command. This is used
50 * for folders not located on IMAP servers, or for IMAP servers that do
51 * not implement the SEARCH command.
54 static enum itoken {
55 ITBAD,
56 ITEOD,
57 ITBOL,
58 ITEOL,
59 ITAND,
60 ITSET,
61 ITALL,
62 ITANSWERED,
63 ITBCC,
64 ITBEFORE,
65 ITBODY,
66 ITCC,
67 ITDELETED,
68 ITDRAFT,
69 ITFLAGGED,
70 ITFROM,
71 ITHEADER,
72 ITKEYWORD,
73 ITLARGER,
74 ITNEW,
75 ITNOT,
76 ITOLD,
77 ITON,
78 ITOR,
79 ITRECENT,
80 ITSEEN,
81 ITSENTBEFORE,
82 ITSENTON,
83 ITSENTSINCE,
84 ITSINCE,
85 ITSMALLER,
86 ITSUBJECT,
87 ITTEXT,
88 ITTO,
89 ITUID,
90 ITUNANSWERED,
91 ITUNDELETED,
92 ITUNDRAFT,
93 ITUNFLAGGED,
94 ITUNKEYWORD,
95 ITUNSEEN
96 } itoken;
98 static unsigned long inumber;
99 static void *iargs[2];
100 static int needheaders;
102 static struct itlex {
103 const char *s_string;
104 enum itoken s_token;
105 } strings[] = {
106 { "ALL", ITALL },
107 { "ANSWERED", ITANSWERED },
108 { "BCC", ITBCC },
109 { "BEFORE", ITBEFORE },
110 { "BODY", ITBODY },
111 { "CC", ITCC },
112 { "DELETED", ITDELETED },
113 { "DRAFT", ITDRAFT },
114 { "FLAGGED", ITFLAGGED },
115 { "FROM", ITFROM },
116 { "HEADER", ITHEADER },
117 { "KEYWORD", ITKEYWORD },
118 { "LARGER", ITLARGER },
119 { "NEW", ITNEW },
120 { "NOT", ITNOT },
121 { "OLD", ITOLD },
122 { "ON", ITON },
123 { "OR", ITOR },
124 { "RECENT", ITRECENT },
125 { "SEEN", ITSEEN },
126 { "SENTBEFORE", ITSENTBEFORE },
127 { "SENTON", ITSENTON },
128 { "SENTSINCE", ITSENTSINCE },
129 { "SINCE", ITSINCE },
130 { "SMALLER", ITSMALLER },
131 { "SUBJECT", ITSUBJECT },
132 { "TEXT", ITTEXT },
133 { "TO", ITTO },
134 { "UID", ITUID },
135 { "UNANSWERED", ITUNANSWERED },
136 { "UNDELETED", ITUNDELETED },
137 { "UNDRAFT", ITUNDRAFT },
138 { "UNFLAGGED", ITUNFLAGGED },
139 { "UNKEYWORD", ITUNKEYWORD },
140 { "UNSEEN", ITUNSEEN },
141 { NULL, ITBAD }
144 static struct itnode {
145 enum itoken n_token;
146 unsigned long n_n;
147 void *n_v;
148 void *n_w;
149 struct itnode *n_x;
150 struct itnode *n_y;
151 } *ittree;
153 static const char *begin;
155 static enum okay itparse(const char *spec, char **xp, int sub);
156 static enum okay itscan(const char *spec, char **xp);
157 static enum okay itsplit(const char *spec, char **xp);
158 static enum okay itstring(void **tp, const char *spec, char **xp);
159 static int itexecute(struct mailbox *mp, struct message *m,
160 int c, struct itnode *n);
161 static int matchfield(struct message *m, const char *field, const char *what);
162 static int matchenvelope(struct message *m, const char *field,
163 const char *what);
164 static char *mkenvelope(struct name *np);
165 static int matchmsg(struct message *m, const char *what, int withheader);
166 static const char *around(const char *cp);
168 enum okay
169 imap_search(const char *spec, int f)
171 static char *lastspec;
172 char *xp;
173 int i;
175 if (strcmp(spec, "()")) {
176 free(lastspec);
177 lastspec = sstrdup(spec);
178 } else if (lastspec == NULL) {
179 fprintf(stderr, "No last SEARCH criteria available.\n");
180 return STOP;
181 } else
182 spec = lastspec;
183 begin = spec;
184 if (imap_search1(spec, f) == OKAY)
185 return OKAY;
186 needheaders = 0;
187 if (itparse(spec, &xp, 0) == STOP)
188 return STOP;
189 if (ittree == NULL)
190 return OKAY;
191 if (mb.mb_type == MB_IMAP && needheaders)
192 imap_getheaders(1, msgCount);
193 for (i = 0; i < msgCount; i++) {
194 if (message[i].m_flag&MHIDDEN)
195 continue;
196 if (f == MDELETED || (message[i].m_flag&MDELETED) == 0)
197 if (itexecute(&mb, &message[i], i+1, ittree))
198 mark(i+1, f);
200 return OKAY;
203 static enum okay
204 itparse(const char *spec, char **xp, int sub)
206 int level = 0;
207 struct itnode n, *z, *_ittree;
208 enum okay ok;
210 ittree = NULL;
211 while ((ok = itscan(spec, xp)) == OKAY && itoken != ITBAD &&
212 itoken != ITEOD) {
213 _ittree = ittree;
214 memset(&n, 0, sizeof n);
215 spec = *xp;
216 switch (itoken) {
217 case ITBOL:
218 level++;
219 continue;
220 case ITEOL:
221 if (--level == 0) {
222 return OKAY;
224 if (level < 0) {
225 if (sub > 0) {
226 (*xp)--;
227 return OKAY;
229 fprintf(stderr, "Excess in \")\".\n");
230 return STOP;
232 continue;
233 case ITNOT:
234 /* <search-key> */
235 n.n_token = ITNOT;
236 if (itparse(spec, xp, sub+1) == STOP)
237 return STOP;
238 spec = *xp;
239 if ((n.n_x = ittree) == NULL) {
240 fprintf(stderr,
241 "Criterion for NOT missing: >>> %s <<<\n",
242 around(*xp));
243 return STOP;
245 itoken = ITNOT;
246 break;
247 case ITOR:
248 /* <search-key1> <search-key2> */
249 n.n_token = ITOR;
250 if (itparse(spec, xp, sub+1) == STOP)
251 return STOP;
252 if ((n.n_x = ittree) == NULL) {
253 fprintf(stderr, "First criterion for OR "
254 "missing: >>> %s <<<\n",
255 around(*xp));
256 return STOP;
258 spec = *xp;
259 if (itparse(spec, xp, sub+1) == STOP)
260 return STOP;
261 spec = *xp;
262 if ((n.n_y = ittree) == NULL) {
263 fprintf(stderr, "Second criterion for OR "
264 "missing: >>> %s <<<\n",
265 around(*xp));
266 return STOP;
268 break;
269 default:
270 n.n_token = itoken;
271 n.n_n = inumber;
272 n.n_v = iargs[0];
273 n.n_w = iargs[1];
275 ittree = _ittree;
276 if (ittree == NULL) {
277 ittree = salloc(sizeof *ittree);
278 *ittree = n;
279 } else {
280 z = ittree;
281 ittree = salloc(sizeof *ittree);
282 ittree->n_token = ITAND;
283 ittree->n_x = z;
284 ittree->n_y = salloc(sizeof*ittree->n_y);
285 *ittree->n_y = n;
287 if (sub && level == 0)
288 break;
290 return ok;
293 static enum okay
294 itscan(const char *spec, char **xp)
296 int i, n;
298 while (spacechar(*spec&0377))
299 spec++;
300 if (*spec == '(') {
301 *xp = (char *)&spec[1];
302 itoken = ITBOL;
303 return OKAY;
305 if (*spec == ')') {
306 *xp = (char *)&spec[1];
307 itoken = ITEOL;
308 return OKAY;
310 while (spacechar(*spec&0377))
311 spec++;
312 if (*spec == '\0') {
313 itoken = ITEOD;
314 return OKAY;
316 for (i = 0; strings[i].s_string; i++) {
317 n = strlen(strings[i].s_string);
318 if (ascncasecmp(spec, strings[i].s_string, n) == 0 &&
319 (spacechar(spec[n]&0377) || spec[n] == '\0'
320 || spec[n] == '(' || spec[n] == ')')) {
321 itoken = strings[i].s_token;
322 spec += n;
323 while (spacechar(*spec&0377))
324 spec++;
325 return itsplit(spec, xp);
328 if (digitchar(*spec&0377)) {
329 inumber = strtoul(spec, xp, 10);
330 if (spacechar(**xp&0377) || **xp == '\0' ||
331 **xp == '(' || **xp == ')') {
332 itoken = ITSET;
333 return OKAY;
336 fprintf(stderr, "Bad SEARCH criterion \"");
337 while (*spec && !spacechar(*spec&0377) &&
338 *spec != '(' && *spec != ')') {
339 putc(*spec&0377, stderr);
340 spec++;
342 fprintf(stderr, "\": >>> %s <<<\n", around(*xp));
343 itoken = ITBAD;
344 return STOP;
347 static enum okay
348 itsplit(const char *spec, char **xp)
350 char *cp;
351 time_t t;
353 switch (itoken) {
354 case ITBCC:
355 case ITBODY:
356 case ITCC:
357 case ITFROM:
358 case ITSUBJECT:
359 case ITTEXT:
360 case ITTO:
361 /* <string> */
362 needheaders++;
363 return itstring(&iargs[0], spec, xp);
364 case ITSENTBEFORE:
365 case ITSENTON:
366 case ITSENTSINCE:
367 needheaders++;
368 /*FALLTHRU*/
369 case ITBEFORE:
370 case ITON:
371 case ITSINCE:
372 /* <date> */
373 if (itstring(&iargs[0], spec, xp) != OKAY)
374 return STOP;
375 if ((t = imap_read_date(iargs[0])) == (time_t)-1) {
376 fprintf(stderr, "Invalid date \"%s\": >>> %s <<<\n",
377 (char *)iargs[0], around(*xp));
378 return STOP;
380 inumber = t;
381 return OKAY;
382 case ITHEADER:
383 /* <field-name> <string> */
384 needheaders++;
385 if (itstring(&iargs[0], spec, xp) != OKAY)
386 return STOP;
387 spec = *xp;
388 return itstring(&iargs[1], spec, xp);
389 case ITKEYWORD:
390 case ITUNKEYWORD:
391 /* <flag> */
392 if (itstring(&iargs[0], spec, xp) != OKAY)
393 return STOP;
394 if (asccasecmp(iargs[0], "\\Seen") == 0)
395 inumber = MREAD;
396 else if (asccasecmp(iargs[0], "\\Deleted") == 0)
397 inumber = MDELETED;
398 else if (asccasecmp(iargs[0], "\\Recent") == 0)
399 inumber = MNEW;
400 else if (asccasecmp(iargs[0], "\\Flagged") == 0)
401 inumber = MFLAGGED;
402 else if (asccasecmp(iargs[0], "\\Answered") == 0)
403 inumber = MANSWERED;
404 else if (asccasecmp(iargs[0], "\\Draft") == 0)
405 inumber = MDRAFT;
406 else
407 inumber = 0;
408 return OKAY;
409 case ITLARGER:
410 case ITSMALLER:
411 /* <n> */
412 if (itstring(&iargs[0], spec, xp) != OKAY)
413 return STOP;
414 inumber = strtoul(iargs[0], &cp, 10);
415 if (spacechar(*cp&0377) || *cp == '\0')
416 return OKAY;
417 fprintf(stderr, "Invalid size: >>> %s <<<\n",
418 around(*xp));
419 return STOP;
420 case ITUID:
421 /* <message set> */
422 fprintf(stderr,
423 "Searching for UIDs is not supported: >>> %s <<<\n",
424 around(*xp));
425 return STOP;
426 default:
427 *xp = (char *)spec;
428 return OKAY;
432 static enum okay
433 itstring(void **tp, const char *spec, char **xp)
435 int inquote = 0;
436 char *ap;
438 while (spacechar(*spec&0377))
439 spec++;
440 if (*spec == '\0' || *spec == '(' || *spec == ')') {
441 fprintf(stderr, "Missing string argument: >>> %s <<<\n",
442 around(&(*xp)[spec - *xp]));
443 return STOP;
445 ap = *tp = salloc(strlen(spec) + 1);
446 *xp = (char *)spec;
447 do {
448 if (inquote && **xp == '\\')
449 *ap++ = *(*xp)++;
450 else if (**xp == '"')
451 inquote = !inquote;
452 else if (!inquote && (spacechar(**xp&0377) ||
453 **xp == '(' || **xp == ')')) {
454 *ap++ = '\0';
455 break;
457 *ap++ = **xp;
458 } while (*(*xp)++);
459 return OKAY;
462 static int
463 itexecute(struct mailbox *mp, struct message *m, int c, struct itnode *n)
465 char *cp, *line = NULL;
466 size_t linesize = 0;
467 FILE *ibuf;
469 if (n == NULL) {
470 fprintf(stderr, "Internal error: Empty node in SEARCH tree.\n");
471 return 0;
473 switch (n->n_token) {
474 case ITBEFORE:
475 case ITON:
476 case ITSINCE:
477 if (m->m_time == 0 && (m->m_flag&MNOFROM) == 0 &&
478 (ibuf = setinput(mp, m, NEED_HEADER)) != NULL) {
479 if (readline(ibuf, &line, &linesize) > 0)
480 m->m_time = unixtime(line);
481 free(line);
483 break;
484 case ITSENTBEFORE:
485 case ITSENTON:
486 case ITSENTSINCE:
487 if (m->m_date == 0)
488 if ((cp = hfield1("date", m)) != NULL)
489 m->m_date = rfctime(cp);
490 break;
491 default:
492 break;
494 switch (n->n_token) {
495 default:
496 fprintf(stderr, "Internal SEARCH error: Lost token %d\n",
497 n->n_token);
498 return 0;
499 case ITAND:
500 return itexecute(mp, m, c, n->n_x) &
501 itexecute(mp, m, c, n->n_y);
502 case ITSET:
503 return (unsigned long)c == n->n_n;
504 case ITALL:
505 return 1;
506 case ITANSWERED:
507 return (m->m_flag&MANSWERED) != 0;
508 case ITBCC:
509 return matchenvelope(m, "bcc", n->n_v);
510 case ITBEFORE:
511 return (unsigned long)m->m_time < n->n_n;
512 case ITBODY:
513 return matchmsg(m, n->n_v, 0);
514 case ITCC:
515 return matchenvelope(m, "cc", n->n_v);
516 case ITDELETED:
517 return (m->m_flag&MDELETED) != 0;
518 case ITDRAFT:
519 return (m->m_flag&MDRAFTED) != 0;
520 case ITFLAGGED:
521 return (m->m_flag&MFLAGGED) != 0;
522 case ITFROM:
523 return matchenvelope(m, "from", n->n_v);
524 case ITHEADER:
525 return matchfield(m, n->n_v, n->n_w);
526 case ITKEYWORD:
527 return (m->m_flag & n->n_n) != 0;
528 case ITLARGER:
529 return m->m_xsize > n->n_n;
530 case ITNEW:
531 return (m->m_flag&(MNEW|MREAD)) == MNEW;
532 case ITNOT:
533 return !itexecute(mp, m, c, n->n_x);
534 case ITOLD:
535 return (m->m_flag&MNEW) == 0;
536 case ITON:
537 return ((unsigned long)m->m_time >= n->n_n &&
538 (unsigned long)m->m_time < n->n_n + 86400);
539 case ITOR:
540 return itexecute(mp, m, c, n->n_x) |
541 itexecute(mp, m, c, n->n_y);
542 case ITRECENT:
543 return (m->m_flag&MNEW) != 0;
544 case ITSEEN:
545 return (m->m_flag&MREAD) != 0;
546 case ITSENTBEFORE:
547 return (unsigned long)m->m_date < n->n_n;
548 case ITSENTON:
549 return ((unsigned long)m->m_date >= n->n_n &&
550 (unsigned long)m->m_date < n->n_n + 86400);
551 case ITSENTSINCE:
552 return (unsigned long)m->m_date >= n->n_n;
553 case ITSINCE:
554 return (unsigned long)m->m_time >= n->n_n;
555 case ITSMALLER:
556 return (unsigned long)m->m_xsize < n->n_n;
557 case ITSUBJECT:
558 return matchfield(m, "subject", n->n_v);
559 case ITTEXT:
560 return matchmsg(m, n->n_v, 1);
561 case ITTO:
562 return matchenvelope(m, "to", n->n_v);
563 case ITUNANSWERED:
564 return (m->m_flag&MANSWERED) == 0;
565 case ITUNDELETED:
566 return (m->m_flag&MDELETED) == 0;
567 case ITUNDRAFT:
568 return (m->m_flag&MDRAFTED) == 0;
569 case ITUNFLAGGED:
570 return (m->m_flag&MFLAGGED) == 0;
571 case ITUNKEYWORD:
572 return (m->m_flag & n->n_n) == 0;
573 case ITUNSEEN:
574 return (m->m_flag&MREAD) == 0;
578 static int
579 matchfield(struct message *m, const char *field, const char *what)
581 struct str in, out;
582 int i;
584 if ((in.s = hfieldX(imap_unquotestr(field), m)) == NULL)
585 return 0;
586 in.l = strlen(in.s);
587 mime_fromhdr(&in, &out, TD_ICONV);
588 what = imap_unquotestr(what);
589 i = substr(out.s, what);
590 free(out.s);
591 return i;
594 static int
595 matchenvelope(struct message *m, const char *field, const char *what)
597 struct name *np;
598 char *cp;
600 if ((cp = hfieldX(imap_unquotestr(field), m)) == NULL)
601 return 0;
602 what = imap_unquotestr(what);
603 np = lextract(cp, GFULL);
604 while (np) {
605 if (substr(np->n_name, what))
606 return 1;
607 if (substr(mkenvelope(np), what))
608 return 1;
609 np = np->n_flink;
611 return 0;
614 static char *
615 mkenvelope(struct name *np)
617 size_t epsize;
618 char *ep;
619 char *realname = NULL, *sourceaddr = NULL,
620 *localpart = NULL, *domainpart = NULL,
621 *cp, *rp, *xp, *ip;
622 struct str in, out;
623 int level = 0, hadphrase = 0;
625 in.s = np->n_fullname;
626 in.l = strlen(in.s);
627 mime_fromhdr(&in, &out, TD_ICONV);
628 rp = ip = ac_alloc(strlen(out.s) + 1);
629 for (cp = out.s; *cp; cp++) {
630 switch (*cp) {
631 case '"':
632 while (*cp) {
633 if (*++cp == '"')
634 break;
635 if (*cp == '\\' && cp[1])
636 cp++;
637 *rp++ = *cp;
639 break;
640 case '<':
641 while (cp > out.s && blankchar(cp[-1]&0377))
642 cp--;
643 rp = ip;
644 xp = out.s;
645 if (xp < &cp[-1] && *xp == '"' && cp[-1] == '"') {
646 xp++;
647 cp--;
649 while (xp < cp)
650 *rp++ = *xp++;
651 hadphrase = 1;
652 goto done;
653 case '(':
654 if (level++)
655 goto dfl;
656 if (hadphrase++ == 0)
657 rp = ip;
658 break;
659 case ')':
660 if (--level)
661 goto dfl;
662 break;
663 case '\\':
664 if (level && cp[1])
665 cp++;
666 goto dfl;
667 default:
668 dfl:
669 *rp++ = *cp;
672 done: *rp = '\0';
673 if (hadphrase)
674 realname = ip;
675 free(out.s);
676 localpart = savestr(np->n_name);
677 if ((cp = strrchr(localpart, '@')) != NULL) {
678 *cp = '\0';
679 domainpart = &cp[1];
681 ep = salloc(epsize = strlen(np->n_fullname) * 2 + 40);
682 snprintf(ep, epsize, "(%s %s %s %s)",
683 realname ? imap_quotestr(realname) : "NIL",
684 sourceaddr ? imap_quotestr(sourceaddr) : "NIL",
685 localpart ? imap_quotestr(localpart) : "NIL",
686 domainpart ? imap_quotestr(domainpart) : "NIL");
687 ac_free(ip);
688 return ep;
691 static int
692 matchmsg(struct message *m, const char *what, int withheader)
694 char *tempFile, *line = NULL;
695 size_t linesize, linelen, count;
696 FILE *fp;
697 int yes = 0;
699 if ((fp = Ftemp(&tempFile, "Ra", "w+", 0600, 1)) == NULL)
700 return 0;
701 rm(tempFile);
702 Ftfree(&tempFile);
703 if (send(m, fp, NULL, NULL, SEND_TOSRCH, NULL) < 0)
704 goto out;
705 fflush(fp);
706 rewind(fp);
707 count = fsize(fp);
708 line = smalloc(linesize = LINESIZE);
709 linelen = 0;
710 if (!withheader)
711 while (fgetline(&line, &linesize, &count, &linelen, fp, 0))
712 if (*line == '\n')
713 break;
714 what = imap_unquotestr(what);
715 while (fgetline(&line, &linesize, &count, &linelen, fp, 0))
716 if (substr(line, what)) {
717 yes = 1;
718 break;
720 out:
721 free(line);
722 Fclose(fp);
723 return yes;
726 #define SURROUNDING 16
727 static const char *
728 around(const char *cp)
730 int i;
731 static char ab[2*SURROUNDING+1];
733 for (i = 0; i < SURROUNDING && cp > begin; i++)
734 cp--;
735 for (i = 0; i < (int)sizeof ab - 1; i++)
736 ab[i] = *cp++;
737 ab[i] = '\0';
738 return ab;