Allow full "RFC 5322 address"es for -b and -c..
[s-mailx.git] / imap_search.c
blob2314572d1aac02bb53825ba5e9e0c2684e8f3706
1 /*
2 * Heirloom mailx - 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 #ifndef lint
41 #ifdef DOSCCS
42 static char sccsid[] = "@(#)imap_search.c 1.29 (gritter) 3/4/06";
43 #endif
44 #endif /* not lint */
46 #include "config.h"
48 #include "rcv.h"
49 #include "extern.h"
50 #include <time.h>
53 * Mail -- a mail program
55 * Client-side implementation of the IMAP SEARCH command. This is used
56 * for folders not located on IMAP servers, or for IMAP servers that do
57 * not implement the SEARCH command.
60 static enum itoken {
61 ITBAD,
62 ITEOD,
63 ITBOL,
64 ITEOL,
65 ITAND,
66 ITSET,
67 ITALL,
68 ITANSWERED,
69 ITBCC,
70 ITBEFORE,
71 ITBODY,
72 ITCC,
73 ITDELETED,
74 ITDRAFT,
75 ITFLAGGED,
76 ITFROM,
77 ITHEADER,
78 ITKEYWORD,
79 ITLARGER,
80 ITNEW,
81 ITNOT,
82 ITOLD,
83 ITON,
84 ITOR,
85 ITRECENT,
86 ITSEEN,
87 ITSENTBEFORE,
88 ITSENTON,
89 ITSENTSINCE,
90 ITSINCE,
91 ITSMALLER,
92 ITSUBJECT,
93 ITTEXT,
94 ITTO,
95 ITUID,
96 ITUNANSWERED,
97 ITUNDELETED,
98 ITUNDRAFT,
99 ITUNFLAGGED,
100 ITUNKEYWORD,
101 ITUNSEEN
102 } itoken;
104 static unsigned long inumber;
105 static void *iargs[2];
106 static int needheaders;
108 static struct itlex {
109 const char *s_string;
110 enum itoken s_token;
111 } strings[] = {
112 { "ALL", ITALL },
113 { "ANSWERED", ITANSWERED },
114 { "BCC", ITBCC },
115 { "BEFORE", ITBEFORE },
116 { "BODY", ITBODY },
117 { "CC", ITCC },
118 { "DELETED", ITDELETED },
119 { "DRAFT", ITDRAFT },
120 { "FLAGGED", ITFLAGGED },
121 { "FROM", ITFROM },
122 { "HEADER", ITHEADER },
123 { "KEYWORD", ITKEYWORD },
124 { "LARGER", ITLARGER },
125 { "NEW", ITNEW },
126 { "NOT", ITNOT },
127 { "OLD", ITOLD },
128 { "ON", ITON },
129 { "OR", ITOR },
130 { "RECENT", ITRECENT },
131 { "SEEN", ITSEEN },
132 { "SENTBEFORE", ITSENTBEFORE },
133 { "SENTON", ITSENTON },
134 { "SENTSINCE", ITSENTSINCE },
135 { "SINCE", ITSINCE },
136 { "SMALLER", ITSMALLER },
137 { "SUBJECT", ITSUBJECT },
138 { "TEXT", ITTEXT },
139 { "TO", ITTO },
140 { "UID", ITUID },
141 { "UNANSWERED", ITUNANSWERED },
142 { "UNDELETED", ITUNDELETED },
143 { "UNDRAFT", ITUNDRAFT },
144 { "UNFLAGGED", ITUNFLAGGED },
145 { "UNKEYWORD", ITUNKEYWORD },
146 { "UNSEEN", ITUNSEEN },
147 { NULL, ITBAD }
150 static struct itnode {
151 enum itoken n_token;
152 unsigned long n_n;
153 void *n_v;
154 void *n_w;
155 struct itnode *n_x;
156 struct itnode *n_y;
157 } *ittree;
159 static const char *begin;
161 static enum okay itparse(const char *spec, char **xp, int sub);
162 static enum okay itscan(const char *spec, char **xp);
163 static enum okay itsplit(const char *spec, char **xp);
164 static enum okay itstring(void **tp, const char *spec, char **xp);
165 static int itexecute(struct mailbox *mp, struct message *m,
166 int c, struct itnode *n);
167 static int matchfield(struct message *m, const char *field, const char *what);
168 static int matchenvelope(struct message *m, const char *field,
169 const char *what);
170 static char *mkenvelope(struct name *np);
171 static int matchmsg(struct message *m, const char *what, int withheader);
172 static const char *around(const char *cp);
174 enum okay
175 imap_search(const char *spec, int f)
177 static char *lastspec;
178 char *xp;
179 int i;
181 if (strcmp(spec, "()")) {
182 free(lastspec);
183 lastspec = sstrdup(spec);
184 } else if (lastspec == NULL) {
185 fprintf(stderr, "No last SEARCH criteria available.\n");
186 return STOP;
187 } else
188 spec = lastspec;
189 begin = spec;
190 if (imap_search1(spec, f) == OKAY)
191 return OKAY;
192 needheaders = 0;
193 if (itparse(spec, &xp, 0) == STOP)
194 return STOP;
195 if (ittree == NULL)
196 return OKAY;
197 if (mb.mb_type == MB_IMAP && needheaders)
198 imap_getheaders(1, msgCount);
199 for (i = 0; i < msgCount; i++) {
200 if (message[i].m_flag&MHIDDEN)
201 continue;
202 if (f == MDELETED || (message[i].m_flag&MDELETED) == 0)
203 if (itexecute(&mb, &message[i], i+1, ittree))
204 mark(i+1, f);
206 return OKAY;
209 static enum okay
210 itparse(const char *spec, char **xp, int sub)
212 int level = 0;
213 struct itnode n, *z, *_ittree;
214 enum okay ok;
216 ittree = NULL;
217 while ((ok = itscan(spec, xp)) == OKAY && itoken != ITBAD &&
218 itoken != ITEOD) {
219 _ittree = ittree;
220 memset(&n, 0, sizeof n);
221 spec = *xp;
222 switch (itoken) {
223 case ITBOL:
224 level++;
225 continue;
226 case ITEOL:
227 if (--level == 0) {
228 return OKAY;
230 if (level < 0) {
231 if (sub > 0) {
232 (*xp)--;
233 return OKAY;
235 fprintf(stderr, "Excess in \")\".\n");
236 return STOP;
238 continue;
239 case ITNOT:
240 /* <search-key> */
241 n.n_token = ITNOT;
242 if (itparse(spec, xp, sub+1) == STOP)
243 return STOP;
244 spec = *xp;
245 if ((n.n_x = ittree) == NULL) {
246 fprintf(stderr,
247 "Criterion for NOT missing: >>> %s <<<\n",
248 around(*xp));
249 return STOP;
251 itoken = ITNOT;
252 break;
253 case ITOR:
254 /* <search-key1> <search-key2> */
255 n.n_token = ITOR;
256 if (itparse(spec, xp, sub+1) == STOP)
257 return STOP;
258 if ((n.n_x = ittree) == NULL) {
259 fprintf(stderr, "First criterion for OR "
260 "missing: >>> %s <<<\n",
261 around(*xp));
262 return STOP;
264 spec = *xp;
265 if (itparse(spec, xp, sub+1) == STOP)
266 return STOP;
267 spec = *xp;
268 if ((n.n_y = ittree) == NULL) {
269 fprintf(stderr, "Second criterion for OR "
270 "missing: >>> %s <<<\n",
271 around(*xp));
272 return STOP;
274 break;
275 default:
276 n.n_token = itoken;
277 n.n_n = inumber;
278 n.n_v = iargs[0];
279 n.n_w = iargs[1];
281 ittree = _ittree;
282 if (ittree == NULL) {
283 ittree = salloc(sizeof *ittree);
284 *ittree = n;
285 } else {
286 z = ittree;
287 ittree = salloc(sizeof *ittree);
288 ittree->n_token = ITAND;
289 ittree->n_x = z;
290 ittree->n_y = salloc(sizeof*ittree->n_y);
291 *ittree->n_y = n;
293 if (sub && level == 0)
294 break;
296 return ok;
299 static enum okay
300 itscan(const char *spec, char **xp)
302 int i, n;
304 while (spacechar(*spec&0377))
305 spec++;
306 if (*spec == '(') {
307 *xp = (char *)&spec[1];
308 itoken = ITBOL;
309 return OKAY;
311 if (*spec == ')') {
312 *xp = (char *)&spec[1];
313 itoken = ITEOL;
314 return OKAY;
316 while (spacechar(*spec&0377))
317 spec++;
318 if (*spec == '\0') {
319 itoken = ITEOD;
320 return OKAY;
322 for (i = 0; strings[i].s_string; i++) {
323 n = strlen(strings[i].s_string);
324 if (ascncasecmp(spec, strings[i].s_string, n) == 0 &&
325 (spacechar(spec[n]&0377) || spec[n] == '\0'
326 || spec[n] == '(' || spec[n] == ')')) {
327 itoken = strings[i].s_token;
328 spec += n;
329 while (spacechar(*spec&0377))
330 spec++;
331 return itsplit(spec, xp);
334 if (digitchar(*spec&0377)) {
335 inumber = strtoul(spec, xp, 10);
336 if (spacechar(**xp&0377) || **xp == '\0' ||
337 **xp == '(' || **xp == ')') {
338 itoken = ITSET;
339 return OKAY;
342 fprintf(stderr, "Bad SEARCH criterion \"");
343 while (*spec && !spacechar(*spec&0377) &&
344 *spec != '(' && *spec != ')') {
345 putc(*spec&0377, stderr);
346 spec++;
348 fprintf(stderr, "\": >>> %s <<<\n", around(*xp));
349 itoken = ITBAD;
350 return STOP;
353 static enum okay
354 itsplit(const char *spec, char **xp)
356 char *cp;
357 time_t t;
359 switch (itoken) {
360 case ITBCC:
361 case ITBODY:
362 case ITCC:
363 case ITFROM:
364 case ITSUBJECT:
365 case ITTEXT:
366 case ITTO:
367 /* <string> */
368 needheaders++;
369 return itstring(&iargs[0], spec, xp);
370 case ITSENTBEFORE:
371 case ITSENTON:
372 case ITSENTSINCE:
373 needheaders++;
374 /*FALLTHRU*/
375 case ITBEFORE:
376 case ITON:
377 case ITSINCE:
378 /* <date> */
379 if (itstring(&iargs[0], spec, xp) != OKAY)
380 return STOP;
381 if ((t = imap_read_date(iargs[0])) == (time_t)-1) {
382 fprintf(stderr, "Invalid date \"%s\": >>> %s <<<\n",
383 (char *)iargs[0], around(*xp));
384 return STOP;
386 inumber = t;
387 return OKAY;
388 case ITHEADER:
389 /* <field-name> <string> */
390 needheaders++;
391 if (itstring(&iargs[0], spec, xp) != OKAY)
392 return STOP;
393 spec = *xp;
394 return itstring(&iargs[1], spec, xp);
395 case ITKEYWORD:
396 case ITUNKEYWORD:
397 /* <flag> */
398 if (itstring(&iargs[0], spec, xp) != OKAY)
399 return STOP;
400 if (asccasecmp(iargs[0], "\\Seen") == 0)
401 inumber = MREAD;
402 else if (asccasecmp(iargs[0], "\\Deleted") == 0)
403 inumber = MDELETED;
404 else if (asccasecmp(iargs[0], "\\Recent") == 0)
405 inumber = MNEW;
406 else if (asccasecmp(iargs[0], "\\Flagged") == 0)
407 inumber = MFLAGGED;
408 else if (asccasecmp(iargs[0], "\\Answered") == 0)
409 inumber = MANSWERED;
410 else if (asccasecmp(iargs[0], "\\Draft") == 0)
411 inumber = MDRAFT;
412 else
413 inumber = 0;
414 return OKAY;
415 case ITLARGER:
416 case ITSMALLER:
417 /* <n> */
418 if (itstring(&iargs[0], spec, xp) != OKAY)
419 return STOP;
420 inumber = strtoul(iargs[0], &cp, 10);
421 if (spacechar(*cp&0377) || *cp == '\0')
422 return OKAY;
423 fprintf(stderr, "Invalid size: >>> %s <<<\n",
424 around(*xp));
425 return STOP;
426 case ITUID:
427 /* <message set> */
428 fprintf(stderr,
429 "Searching for UIDs is not supported: >>> %s <<<\n",
430 around(*xp));
431 return STOP;
432 default:
433 *xp = (char *)spec;
434 return OKAY;
438 static enum okay
439 itstring(void **tp, const char *spec, char **xp)
441 int inquote = 0;
442 char *ap;
444 while (spacechar(*spec&0377))
445 spec++;
446 if (*spec == '\0' || *spec == '(' || *spec == ')') {
447 fprintf(stderr, "Missing string argument: >>> %s <<<\n",
448 around(&(*xp)[spec - *xp]));
449 return STOP;
451 ap = *tp = salloc(strlen(spec) + 1);
452 *xp = (char *)spec;
453 do {
454 if (inquote && **xp == '\\')
455 *ap++ = *(*xp)++;
456 else if (**xp == '"')
457 inquote = !inquote;
458 else if (!inquote && (spacechar(**xp&0377) ||
459 **xp == '(' || **xp == ')')) {
460 *ap++ = '\0';
461 break;
463 *ap++ = **xp;
464 } while (*(*xp)++);
465 return OKAY;
468 static int
469 itexecute(struct mailbox *mp, struct message *m, int c, struct itnode *n)
471 char *cp, *line = NULL;
472 size_t linesize = 0;
473 FILE *ibuf;
475 if (n == NULL) {
476 fprintf(stderr, "Internal error: Empty node in SEARCH tree.\n");
477 return 0;
479 switch (n->n_token) {
480 case ITBEFORE:
481 case ITON:
482 case ITSINCE:
483 if (m->m_time == 0 && (m->m_flag&MNOFROM) == 0 &&
484 (ibuf = setinput(mp, m, NEED_HEADER)) != NULL) {
485 if (readline(ibuf, &line, &linesize) > 0)
486 m->m_time = unixtime(line);
487 free(line);
489 break;
490 case ITSENTBEFORE:
491 case ITSENTON:
492 case ITSENTSINCE:
493 if (m->m_date == 0)
494 if ((cp = hfield("date", m)) != NULL)
495 m->m_date = rfctime(cp);
496 break;
497 default:
498 break;
500 switch (n->n_token) {
501 default:
502 fprintf(stderr, "Internal SEARCH error: Lost token %d\n",
503 n->n_token);
504 return 0;
505 case ITAND:
506 return itexecute(mp, m, c, n->n_x) &
507 itexecute(mp, m, c, n->n_y);
508 case ITSET:
509 return (unsigned long)c == n->n_n;
510 case ITALL:
511 return 1;
512 case ITANSWERED:
513 return (m->m_flag&MANSWERED) != 0;
514 case ITBCC:
515 return matchenvelope(m, "bcc", n->n_v);
516 case ITBEFORE:
517 return (unsigned long)m->m_time < n->n_n;
518 case ITBODY:
519 return matchmsg(m, n->n_v, 0);
520 case ITCC:
521 return matchenvelope(m, "cc", n->n_v);
522 case ITDELETED:
523 return (m->m_flag&MDELETED) != 0;
524 case ITDRAFT:
525 return (m->m_flag&MDRAFTED) != 0;
526 case ITFLAGGED:
527 return (m->m_flag&MFLAGGED) != 0;
528 case ITFROM:
529 return matchenvelope(m, "from", n->n_v);
530 case ITHEADER:
531 return matchfield(m, n->n_v, n->n_w);
532 case ITKEYWORD:
533 return (m->m_flag & n->n_n) != 0;
534 case ITLARGER:
535 return m->m_xsize > n->n_n;
536 case ITNEW:
537 return (m->m_flag&(MNEW|MREAD)) == MNEW;
538 case ITNOT:
539 return !itexecute(mp, m, c, n->n_x);
540 case ITOLD:
541 return (m->m_flag&MNEW) == 0;
542 case ITON:
543 return ((unsigned long)m->m_time >= n->n_n &&
544 (unsigned long)m->m_time < n->n_n + 86400);
545 case ITOR:
546 return itexecute(mp, m, c, n->n_x) |
547 itexecute(mp, m, c, n->n_y);
548 case ITRECENT:
549 return (m->m_flag&MNEW) != 0;
550 case ITSEEN:
551 return (m->m_flag&MREAD) != 0;
552 case ITSENTBEFORE:
553 return (unsigned long)m->m_date < n->n_n;
554 case ITSENTON:
555 return ((unsigned long)m->m_date >= n->n_n &&
556 (unsigned long)m->m_date < n->n_n + 86400);
557 case ITSENTSINCE:
558 return (unsigned long)m->m_date >= n->n_n;
559 case ITSINCE:
560 return (unsigned long)m->m_time >= n->n_n;
561 case ITSMALLER:
562 return (unsigned long)m->m_xsize < n->n_n;
563 case ITSUBJECT:
564 return matchfield(m, "subject", n->n_v);
565 case ITTEXT:
566 return matchmsg(m, n->n_v, 1);
567 case ITTO:
568 return matchenvelope(m, "to", n->n_v);
569 case ITUNANSWERED:
570 return (m->m_flag&MANSWERED) == 0;
571 case ITUNDELETED:
572 return (m->m_flag&MDELETED) == 0;
573 case ITUNDRAFT:
574 return (m->m_flag&MDRAFTED) == 0;
575 case ITUNFLAGGED:
576 return (m->m_flag&MFLAGGED) == 0;
577 case ITUNKEYWORD:
578 return (m->m_flag & n->n_n) == 0;
579 case ITUNSEEN:
580 return (m->m_flag&MREAD) == 0;
584 static int
585 matchfield(struct message *m, const char *field, const char *what)
587 struct str in, out;
588 int i;
590 if ((in.s = hfield(imap_unquotestr(field), m)) == NULL)
591 return 0;
592 in.l = strlen(in.s);
593 mime_fromhdr(&in, &out, TD_ICONV);
594 what = imap_unquotestr(what);
595 i = substr(out.s, what);
596 free(out.s);
597 return i;
600 static int
601 matchenvelope(struct message *m, const char *field, const char *what)
603 struct name *np;
604 char *cp;
606 if ((cp = hfield(imap_unquotestr(field), m)) == NULL)
607 return 0;
608 what = imap_unquotestr(what);
609 np = sextract(cp, GFULL);
610 while (np) {
611 if (substr(np->n_name, what))
612 return 1;
613 if (substr(mkenvelope(np), what))
614 return 1;
615 np = np->n_flink;
617 return 0;
620 static char *
621 mkenvelope(struct name *np)
623 size_t epsize;
624 char *ep;
625 char *realname = NULL, *sourceaddr = NULL,
626 *localpart = NULL, *domainpart = NULL,
627 *cp, *rp, *xp, *ip;
628 struct str in, out;
629 int level = 0, hadphrase = 0;
631 in.s = np->n_fullname;
632 in.l = strlen(in.s);
633 mime_fromhdr(&in, &out, TD_ICONV);
634 rp = ip = ac_alloc(strlen(out.s) + 1);
635 for (cp = out.s; *cp; cp++) {
636 switch (*cp) {
637 case '"':
638 while (*cp) {
639 if (*++cp == '"')
640 break;
641 if (*cp == '\\' && cp[1])
642 cp++;
643 *rp++ = *cp;
645 break;
646 case '<':
647 while (cp > out.s && blankchar(cp[-1]&0377))
648 cp--;
649 rp = ip;
650 xp = out.s;
651 if (xp < &cp[-1] && *xp == '"' && cp[-1] == '"') {
652 xp++;
653 cp--;
655 while (xp < cp)
656 *rp++ = *xp++;
657 hadphrase = 1;
658 goto done;
659 case '(':
660 if (level++)
661 goto dfl;
662 if (hadphrase++ == 0)
663 rp = ip;
664 break;
665 case ')':
666 if (--level)
667 goto dfl;
668 break;
669 case '\\':
670 if (level && cp[1])
671 cp++;
672 goto dfl;
673 default:
674 dfl:
675 *rp++ = *cp;
678 done: *rp = '\0';
679 if (hadphrase)
680 realname = ip;
681 free(out.s);
682 localpart = savestr(np->n_name);
683 if ((cp = strrchr(localpart, '@')) != NULL) {
684 *cp = '\0';
685 domainpart = &cp[1];
687 ep = salloc(epsize = strlen(np->n_fullname) * 2 + 40);
688 snprintf(ep, epsize, "(%s %s %s %s)",
689 realname ? imap_quotestr(realname) : "NIL",
690 sourceaddr ? imap_quotestr(sourceaddr) : "NIL",
691 localpart ? imap_quotestr(localpart) : "NIL",
692 domainpart ? imap_quotestr(domainpart) : "NIL");
693 ac_free(ip);
694 return ep;
697 static int
698 matchmsg(struct message *m, const char *what, int withheader)
700 char *tempFile, *line = NULL;
701 size_t linesize, linelen, count;
702 FILE *fp;
703 int yes = 0;
705 if ((fp = Ftemp(&tempFile, "Ra", "w+", 0600, 1)) == NULL)
706 return 0;
707 rm(tempFile);
708 Ftfree(&tempFile);
709 if (send(m, fp, NULL, NULL, SEND_TOSRCH, NULL) < 0)
710 goto out;
711 fflush(fp);
712 rewind(fp);
713 count = fsize(fp);
714 line = smalloc(linesize = LINESIZE);
715 linelen = 0;
716 if (!withheader)
717 while (fgetline(&line, &linesize, &count, &linelen, fp, 0))
718 if (*line == '\n')
719 break;
720 what = imap_unquotestr(what);
721 while (fgetline(&line, &linesize, &count, &linelen, fp, 0))
722 if (substr(line, what)) {
723 yes = 1;
724 break;
726 out:
727 free(line);
728 Fclose(fp);
729 return yes;
732 #define SURROUNDING 16
733 static const char *
734 around(const char *cp)
736 int i;
737 static char ab[2*SURROUNDING+1];
739 for (i = 0; i < SURROUNDING && cp > begin; i++)
740 cp--;
741 for (i = 0; i < (int)sizeof ab - 1; i++)
742 ab[i] = *cp++;
743 ab[i] = '\0';
744 return ab;