Merge branch 'topic/amalgam'
[s-mailx.git] / imap_search.c
blobab0b7441bfc929514f24e3cdb2a0dbe144a065e9
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 #ifndef HAVE_AMALGAMATION
43 # include "nail.h"
44 #endif
46 static enum itoken {
47 ITBAD,
48 ITEOD,
49 ITBOL,
50 ITEOL,
51 ITAND,
52 ITSET,
53 ITALL,
54 ITANSWERED,
55 ITBCC,
56 ITBEFORE,
57 ITBODY,
58 ITCC,
59 ITDELETED,
60 ITDRAFT,
61 ITFLAGGED,
62 ITFROM,
63 ITHEADER,
64 ITKEYWORD,
65 ITLARGER,
66 ITNEW,
67 ITNOT,
68 ITOLD,
69 ITON,
70 ITOR,
71 ITRECENT,
72 ITSEEN,
73 ITSENTBEFORE,
74 ITSENTON,
75 ITSENTSINCE,
76 ITSINCE,
77 ITSMALLER,
78 ITSUBJECT,
79 ITTEXT,
80 ITTO,
81 ITUID,
82 ITUNANSWERED,
83 ITUNDELETED,
84 ITUNDRAFT,
85 ITUNFLAGGED,
86 ITUNKEYWORD,
87 ITUNSEEN
88 } itoken;
90 static unsigned long inumber;
91 static void *iargs[2];
92 static int needheaders;
94 static struct itlex {
95 const char *s_string;
96 enum itoken s_token;
97 } strings[] = {
98 { "ALL", ITALL },
99 { "ANSWERED", ITANSWERED },
100 { "BCC", ITBCC },
101 { "BEFORE", ITBEFORE },
102 { "BODY", ITBODY },
103 { "CC", ITCC },
104 { "DELETED", ITDELETED },
105 { "DRAFT", ITDRAFT },
106 { "FLAGGED", ITFLAGGED },
107 { "FROM", ITFROM },
108 { "HEADER", ITHEADER },
109 { "KEYWORD", ITKEYWORD },
110 { "LARGER", ITLARGER },
111 { "NEW", ITNEW },
112 { "NOT", ITNOT },
113 { "OLD", ITOLD },
114 { "ON", ITON },
115 { "OR", ITOR },
116 { "RECENT", ITRECENT },
117 { "SEEN", ITSEEN },
118 { "SENTBEFORE", ITSENTBEFORE },
119 { "SENTON", ITSENTON },
120 { "SENTSINCE", ITSENTSINCE },
121 { "SINCE", ITSINCE },
122 { "SMALLER", ITSMALLER },
123 { "SUBJECT", ITSUBJECT },
124 { "TEXT", ITTEXT },
125 { "TO", ITTO },
126 { "UID", ITUID },
127 { "UNANSWERED", ITUNANSWERED },
128 { "UNDELETED", ITUNDELETED },
129 { "UNDRAFT", ITUNDRAFT },
130 { "UNFLAGGED", ITUNFLAGGED },
131 { "UNKEYWORD", ITUNKEYWORD },
132 { "UNSEEN", ITUNSEEN },
133 { NULL, ITBAD }
136 static struct itnode {
137 enum itoken n_token;
138 unsigned long n_n;
139 void *n_v;
140 void *n_w;
141 struct itnode *n_x;
142 struct itnode *n_y;
143 } *ittree;
145 static const char *begin;
147 static enum okay itparse(char const *spec, char const **xp, int sub);
148 static enum okay itscan(char const *spec, char const **xp);
149 static enum okay itsplit(char const *spec, char const **xp);
150 static enum okay itstring(void **tp, char const *spec, char const **xp);
151 static int itexecute(struct mailbox *mp, struct message *m,
152 int c, struct itnode *n);
153 static int matchfield(struct message *m, const char *field, const char *what);
154 static int matchenvelope(struct message *m, const char *field,
155 const char *what);
156 static char *mkenvelope(struct name *np);
157 static int matchmsg(struct message *m, const char *what, int withheader);
158 static const char *around(const char *cp);
160 FL enum okay
161 imap_search(const char *spec, int f)
163 static char *lastspec;
164 char const *xp;
165 int i;
167 if (strcmp(spec, "()")) {
168 free(lastspec);
169 lastspec = sstrdup(spec);
170 } else if (lastspec == NULL) {
171 fprintf(stderr, "No last SEARCH criteria available.\n");
172 return STOP;
173 } else
174 spec = lastspec;
175 begin = spec;
176 #ifdef HAVE_IMAP
177 if (imap_search1(spec, f) == OKAY)
178 return OKAY;
179 needheaders = 0;
180 #endif
181 if (itparse(spec, &xp, 0) == STOP)
182 return STOP;
183 if (ittree == NULL)
184 return OKAY;
185 #ifdef HAVE_IMAP
186 if (mb.mb_type == MB_IMAP && needheaders)
187 imap_getheaders(1, msgCount);
188 #endif
189 srelax_hold();
190 for (i = 0; i < msgCount; i++) {
191 if (message[i].m_flag&MHIDDEN)
192 continue;
193 if (f == MDELETED || (message[i].m_flag&MDELETED) == 0) {
194 if (itexecute(&mb, &message[i], i+1, ittree))
195 mark(i+1, f);
196 srelax();
199 srelax_rele();
200 return OKAY;
203 static enum okay
204 itparse(char const *spec, char const **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(char const *spec, char const **xp)
296 int i, n;
298 while (spacechar(*spec))
299 spec++;
300 if (*spec == '(') {
301 *xp = &spec[1];
302 itoken = ITBOL;
303 return OKAY;
305 if (*spec == ')') {
306 *xp = &spec[1];
307 itoken = ITEOL;
308 return OKAY;
310 while (spacechar(*spec))
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)) {
329 inumber = strtoul(spec, UNCONST(xp), 10);
330 if (spacechar(**xp) || **xp == '\0' ||
331 **xp == '(' || **xp == ')') {
332 itoken = ITSET;
333 return OKAY;
336 fprintf(stderr, "Bad SEARCH criterion \"");
337 while (*spec && !spacechar(*spec) &&
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(char const *spec, char const **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 = spec;
428 return OKAY;
432 static enum okay
433 itstring(void **tp, char const *spec, char const **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 = 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_restart(ibuf, &line, &linesize, 0) > 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 *realnam = 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 realnam = 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 realnam ? imap_quotestr(realnam) : "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, cnt;
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 (sendmp(m, fp, NULL, NULL, SEND_TOSRCH, NULL) < 0)
704 goto out;
705 fflush(fp);
706 rewind(fp);
707 cnt = fsize(fp);
708 line = smalloc(linesize = LINESIZE);
709 linelen = 0;
710 if (!withheader)
711 while (fgetline(&line, &linesize, &cnt, &linelen, fp, 0))
712 if (*line == '\n')
713 break;
714 what = imap_unquotestr(what);
715 while (fgetline(&line, &linesize, &cnt, &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;