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