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