cc-test.sh: do a simple \`Resend' test
[s-mailx.git] / imap_search.c
blobc98978195ee4b2ac1932fc7b3f934df0d24d818c
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 - 2015 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.
41 #undef n_FILE
42 #define n_FILE imap_search
44 #ifndef HAVE_AMALGAMATION
45 # include "nail.h"
46 #endif
48 EMPTY_FILE()
49 #ifdef HAVE_IMAP_SEARCH
51 enum itoken {
52 ITBAD, ITEOD, ITBOL, ITEOL, ITAND, ITSET, ITALL, ITANSWERED,
53 ITBCC, ITBEFORE, ITBODY,
54 ITCC,
55 ITDELETED, ITDRAFT,
56 ITFLAGGED, ITFROM,
57 ITHEADER,
58 ITKEYWORD,
59 ITLARGER,
60 ITNEW, ITNOT,
61 ITOLD, ITON, ITOR,
62 ITRECENT,
63 ITSEEN, ITSENTBEFORE, ITSENTON, ITSENTSINCE, ITSINCE, ITSMALLER,
64 ITSUBJECT,
65 ITTEXT, ITTO,
66 ITUID, ITUNANSWERED, ITUNDELETED, ITUNDRAFT, ITUNFLAGGED, ITUNKEYWORD,
67 ITUNSEEN
70 struct itlex {
71 char const *s_string;
72 enum itoken s_token;
75 struct itnode {
76 enum itoken n_token;
77 unsigned long n_n;
78 void *n_v;
79 void *n_w;
80 struct itnode *n_x;
81 struct itnode *n_y;
84 static struct itlex const _it_strings[] = {
85 { "ALL", ITALL },
86 { "ANSWERED", ITANSWERED },
87 { "BCC", ITBCC },
88 { "BEFORE", ITBEFORE },
89 { "BODY", ITBODY },
90 { "CC", ITCC },
91 { "DELETED", ITDELETED },
92 { "DRAFT", ITDRAFT },
93 { "FLAGGED", ITFLAGGED },
94 { "FROM", ITFROM },
95 { "HEADER", ITHEADER },
96 { "KEYWORD", ITKEYWORD },
97 { "LARGER", ITLARGER },
98 { "NEW", ITNEW },
99 { "NOT", ITNOT },
100 { "OLD", ITOLD },
101 { "ON", ITON },
102 { "OR", ITOR },
103 { "RECENT", ITRECENT },
104 { "SEEN", ITSEEN },
105 { "SENTBEFORE", ITSENTBEFORE },
106 { "SENTON", ITSENTON },
107 { "SENTSINCE", ITSENTSINCE },
108 { "SINCE", ITSINCE },
109 { "SMALLER", ITSMALLER },
110 { "SUBJECT", ITSUBJECT },
111 { "TEXT", ITTEXT },
112 { "TO", ITTO },
113 { "UID", ITUID },
114 { "UNANSWERED", ITUNANSWERED },
115 { "UNDELETED", ITUNDELETED },
116 { "UNDRAFT", ITUNDRAFT },
117 { "UNFLAGGED", ITUNFLAGGED },
118 { "UNKEYWORD", ITUNKEYWORD },
119 { "UNSEEN", ITUNSEEN },
120 { NULL, ITBAD }
123 static struct itnode *_it_tree;
124 static char *_it_begin;
125 static enum itoken _it_token;
126 static unsigned long _it_number;
127 static void *_it_args[2];
128 static size_t _it_need_headers;
130 static enum okay itparse(char const *spec, char const **xp, int sub);
131 static enum okay itscan(char const *spec, char const **xp);
132 static enum okay itsplit(char const *spec, char const **xp);
133 static enum okay itstring(void **tp, char const *spec, char const **xp);
134 static int itexecute(struct mailbox *mp, struct message *m,
135 size_t c, struct itnode *n);
136 static time_t _imap_read_date(char const *cp);
137 static bool_t matchfield(struct message *m, char const *field,
138 void const *what);
139 static int matchenvelope(struct message *m, char const *field,
140 void const *what);
141 static char * mkenvelope(struct name *np);
142 static char const * around(char const *cp);
144 static enum okay
145 itparse(char const *spec, char const **xp, int sub)
147 int level = 0;
148 struct itnode n, *z, *ittree;
149 enum okay rv;
150 NYD_ENTER;
152 _it_tree = NULL;
153 while ((rv = itscan(spec, xp)) == OKAY && _it_token != ITBAD &&
154 _it_token != ITEOD) {
155 ittree = _it_tree;
156 memset(&n, 0, sizeof n);
157 spec = *xp;
158 switch (_it_token) {
159 case ITBOL:
160 ++level;
161 continue;
162 case ITEOL:
163 if (--level == 0)
164 goto jleave;
165 if (level < 0) {
166 if (sub > 0) {
167 --(*xp);
168 goto jleave;
170 n_err(_("Excess in \")\"\n"));
171 rv = STOP;
172 goto jleave;
174 continue;
175 case ITNOT:
176 /* <search-key> */
177 n.n_token = ITNOT;
178 if ((rv = itparse(spec, xp, sub + 1)) == STOP)
179 goto jleave;
180 spec = *xp;
181 if ((n.n_x = _it_tree) == NULL) {
182 n_err(_("Criterion for NOT missing: >>> %s <<<\n"), around(*xp));
183 rv = STOP;
184 goto jleave;
186 _it_token = ITNOT;
187 break;
188 case ITOR:
189 /* <search-key1> <search-key2> */
190 n.n_token = ITOR;
191 if ((rv = itparse(spec, xp, sub + 1)) == STOP)
192 goto jleave;
193 if ((n.n_x = _it_tree) == NULL) {
194 n_err(_("First criterion for OR missing: >>> %s <<<\n"),
195 around(*xp));
196 rv = STOP;
197 goto jleave;
199 spec = *xp;
200 if ((rv = itparse(spec, xp, sub + 1)) == STOP)
201 goto jleave;
202 spec = *xp;
203 if ((n.n_y = _it_tree) == NULL) {
204 n_err(_("Second criterion for OR missing: >>> %s <<<\n"),
205 around(*xp));
206 rv = STOP;
207 goto jleave;
209 break;
210 default:
211 n.n_token = _it_token;
212 n.n_n = _it_number;
213 n.n_v = _it_args[0];
214 n.n_w = _it_args[1];
217 _it_tree = ittree;
218 if (_it_tree == NULL) {
219 _it_tree = salloc(sizeof *_it_tree);
220 *_it_tree = n;
221 } else {
222 z = _it_tree;
223 _it_tree = salloc(sizeof *_it_tree);
224 _it_tree->n_token = ITAND;
225 _it_tree->n_x = z;
226 _it_tree->n_y = salloc(sizeof *_it_tree->n_y);
227 *_it_tree->n_y = n;
229 if (sub && level == 0)
230 break;
232 jleave:
233 NYD_LEAVE;
234 return rv;
237 static enum okay
238 itscan(char const *spec, char const **xp)
240 int i, n;
241 enum okay rv = OKAY;
242 NYD_ENTER;
244 while (spacechar(*spec))
245 ++spec;
246 if (*spec == '(') {
247 *xp = &spec[1];
248 _it_token = ITBOL;
249 goto jleave;
251 if (*spec == ')') {
252 *xp = &spec[1];
253 _it_token = ITEOL;
254 goto jleave;
256 while (spacechar(*spec))
257 ++spec;
258 if (*spec == '\0') {
259 _it_token = ITEOD;
260 goto jleave;
263 #define __GO(C) ((C) != '\0' && (C) != '(' && (C) != ')' && !spacechar(C))
264 for (i = 0; _it_strings[i].s_string != NULL; ++i) {
265 n = strlen(_it_strings[i].s_string);
266 if (!ascncasecmp(spec, _it_strings[i].s_string, n) && !__GO(spec[n])) {
267 _it_token = _it_strings[i].s_token;
268 spec += n;
269 while (spacechar(*spec))
270 ++spec;
271 rv = itsplit(spec, xp);
272 goto jleave;
275 if (digitchar(*spec)) {
276 _it_number = strtoul(spec, UNCONST(xp), 10);
277 if (!__GO(**xp)) {
278 _it_token = ITSET;
279 goto jleave;
283 n_err(_("Bad SEARCH criterion \""));
284 for (i = 0; __GO(spec[i]); ++i)
286 n_err(_("%.*s \": >>> %s <<<\n"), i, spec, around(*xp));
287 #undef __GO
289 _it_token = ITBAD;
290 rv = STOP;
291 jleave:
292 NYD_LEAVE;
293 return rv;
296 static enum okay
297 itsplit(char const *spec, char const **xp)
299 char *cp;
300 time_t t;
301 enum okay rv;
302 NYD_ENTER;
304 switch (_it_token) {
305 case ITBCC:
306 case ITBODY:
307 case ITCC:
308 case ITFROM:
309 case ITSUBJECT:
310 case ITTEXT:
311 case ITTO:
312 /* <string> */
313 ++_it_need_headers;
314 rv = itstring(_it_args, spec, xp);
315 break;
316 case ITSENTBEFORE:
317 case ITSENTON:
318 case ITSENTSINCE:
319 ++_it_need_headers;
320 /*FALLTHRU*/
321 case ITBEFORE:
322 case ITON:
323 case ITSINCE:
324 /* <date> */
325 if ((rv = itstring(_it_args, spec, xp)) != OKAY)
326 break;
327 if ((t = _imap_read_date(_it_args[0])) == (time_t)-1) {
328 n_err(_("Invalid date \"%s\": >>> %s <<<\n"),
329 (char*)_it_args[0], around(*xp));
330 rv = STOP;
331 break;
333 _it_number = t;
334 rv = OKAY;
335 break;
336 case ITHEADER:
337 /* <field-name> <string> */
338 ++_it_need_headers;
339 if ((rv = itstring(_it_args, spec, xp)) != OKAY)
340 break;
341 spec = *xp;
342 if ((rv = itstring(_it_args + 1, spec, xp)) != OKAY)
343 break;
344 break;
345 case ITKEYWORD:
346 case ITUNKEYWORD:
347 /* <flag> */ /* TODO use table->flag map search instead */
348 if ((rv = itstring(_it_args, spec, xp)) != OKAY)
349 break;
350 if (!asccasecmp(_it_args[0], "\\Seen"))
351 _it_number = MREAD;
352 else if (!asccasecmp(_it_args[0], "\\Deleted"))
353 _it_number = MDELETED;
354 else if (!asccasecmp(_it_args[0], "\\Recent"))
355 _it_number = MNEW;
356 else if (!asccasecmp(_it_args[0], "\\Flagged"))
357 _it_number = MFLAGGED;
358 else if (!asccasecmp(_it_args[0], "\\Answered"))
359 _it_number = MANSWERED;
360 else if (!asccasecmp(_it_args[0], "\\Draft"))
361 _it_number = MDRAFT;
362 else
363 _it_number = 0;
364 break;
365 case ITLARGER:
366 case ITSMALLER:
367 /* <n> */
368 if ((rv = itstring(_it_args, spec, xp)) != OKAY)
369 break;
370 _it_number = strtoul(_it_args[0], &cp, 10);
371 if (spacechar(*cp) || *cp == '\0')
372 break;
373 n_err(_("Invalid size: >>> %s <<<\n"), around(*xp));
374 rv = STOP;
375 break;
376 case ITUID:
377 /* <message set> */
378 n_err(_("Searching for UIDs is not supported: >>> %s <<<\n"),
379 around(*xp));
380 rv = STOP;
381 break;
382 default:
383 *xp = spec;
384 rv = OKAY;
385 break;
387 NYD_LEAVE;
388 return rv;
391 static enum okay
392 itstring(void **tp, char const *spec, char const **xp) /* XXX lesser derefs */
394 int inquote = 0;
395 char *ap;
396 enum okay rv = STOP;
397 NYD_ENTER;
399 while (spacechar(*spec))
400 ++spec;
401 if (*spec == '\0' || *spec == '(' || *spec == ')') {
402 n_err(_("Missing string argument: >>> %s <<<\n"),
403 around(&(*xp)[spec - *xp]));
404 goto jleave;
406 ap = *tp = salloc(strlen(spec) +1);
407 *xp = spec;
408 do {
409 if (inquote && **xp == '\\')
410 *ap++ = *(*xp)++;
411 else if (**xp == '"')
412 inquote = !inquote;
413 else if (!inquote && (spacechar(**xp) || **xp == '(' || **xp == ')')) {
414 *ap++ = '\0';
415 break;
417 *ap++ = **xp;
418 } while (*(*xp)++);
420 *tp = imap_unquotestr(*tp);
421 rv = OKAY;
422 jleave:
423 NYD_LEAVE;
424 return rv;
427 static int
428 itexecute(struct mailbox *mp, struct message *m, size_t c, struct itnode *n)
430 struct search_expr se;
431 char *cp, *line = NULL; /* TODO line pool */
432 size_t linesize = 0;
433 FILE *ibuf;
434 int rv;
435 NYD_ENTER;
437 if (n == NULL) {
438 n_err(_("Internal error: Empty node in SEARCH tree\n"));
439 rv = 0;
440 goto jleave;
443 switch (n->n_token) {
444 case ITBEFORE:
445 case ITON:
446 case ITSINCE:
447 if (m->m_time == 0 && !(m->m_flag & MNOFROM) &&
448 (ibuf = setinput(mp, m, NEED_HEADER)) != NULL) {
449 if (readline_restart(ibuf, &line, &linesize, 0) > 0)
450 m->m_time = unixtime(line);
451 free(line);
453 break;
454 case ITSENTBEFORE:
455 case ITSENTON:
456 case ITSENTSINCE:
457 if (m->m_date == 0)
458 if ((cp = hfield1("date", m)) != NULL)
459 m->m_date = rfctime(cp);
460 break;
461 default:
462 break;
465 switch (n->n_token) {
466 default:
467 n_err(_("Internal SEARCH error: Lost token %d\n"), n->n_token);
468 rv = 0;
469 break;
470 case ITAND:
471 rv = itexecute(mp, m, c, n->n_x) & itexecute(mp, m, c, n->n_y);
472 break;
473 case ITSET:
474 rv = UICMP(z, c, ==, n->n_n);
475 break;
476 case ITALL:
477 rv = 1;
478 break;
479 case ITANSWERED:
480 rv = ((m->m_flag & MANSWERED) != 0);
481 break;
482 case ITBCC:
483 rv = matchenvelope(m, "bcc", n->n_v);
484 break;
485 case ITBEFORE:
486 rv = UICMP(z, m->m_time, <, n->n_n);
487 break;
488 case ITBODY:
489 se.ss_sexpr = n->n_v;
490 se.ss_where = "body";
491 rv = message_match(m, &se, FAL0);
492 break;
493 case ITCC:
494 rv = matchenvelope(m, "cc", n->n_v);
495 break;
496 case ITDELETED:
497 rv = ((m->m_flag & MDELETED) != 0);
498 break;
499 case ITDRAFT:
500 rv = ((m->m_flag & MDRAFTED) != 0);
501 break;
502 case ITFLAGGED:
503 rv = ((m->m_flag & MFLAGGED) != 0);
504 break;
505 case ITFROM:
506 rv = matchenvelope(m, "from", n->n_v);
507 break;
508 case ITHEADER:
509 rv = matchfield(m, n->n_v, n->n_w);
510 break;
511 case ITKEYWORD:
512 rv = ((m->m_flag & n->n_n) != 0);
513 break;
514 case ITLARGER:
515 rv = (m->m_xsize > n->n_n);
516 break;
517 case ITNEW:
518 rv = ((m->m_flag & (MNEW | MREAD)) == MNEW);
519 break;
520 case ITNOT:
521 rv = !itexecute(mp, m, c, n->n_x);
522 break;
523 case ITOLD:
524 rv = !(m->m_flag & MNEW);
525 break;
526 case ITON:
527 rv = (UICMP(z, m->m_time, >=, n->n_n) &&
528 UICMP(z, m->m_time, <, n->n_n + 86400));
529 break;
530 case ITOR:
531 rv = itexecute(mp, m, c, n->n_x) | itexecute(mp, m, c, n->n_y);
532 break;
533 case ITRECENT:
534 rv = ((m->m_flag & MNEW) != 0);
535 break;
536 case ITSEEN:
537 rv = ((m->m_flag & MREAD) != 0);
538 break;
539 case ITSENTBEFORE:
540 rv = UICMP(z, m->m_date, <, n->n_n);
541 break;
542 case ITSENTON:
543 rv = (UICMP(z, m->m_date, >=, n->n_n) &&
544 UICMP(z, m->m_date, <, n->n_n + 86400));
545 break;
546 case ITSENTSINCE:
547 rv = UICMP(z, m->m_date, >=, n->n_n);
548 break;
549 case ITSINCE:
550 rv = UICMP(z, m->m_time, >=, n->n_n);
551 break;
552 case ITSMALLER:
553 rv = UICMP(z, m->m_xsize, <, n->n_n);
554 break;
555 case ITSUBJECT:
556 rv = matchfield(m, "subject", n->n_v);
557 break;
558 case ITTEXT:
559 se.ss_sexpr = n->n_v;
560 se.ss_where = "text";
561 rv = message_match(m, &se, TRU1);
562 break;
563 case ITTO:
564 rv = matchenvelope(m, "to", n->n_v);
565 break;
566 case ITUNANSWERED:
567 rv = !(m->m_flag & MANSWERED);
568 break;
569 case ITUNDELETED:
570 rv = !(m->m_flag & MDELETED);
571 break;
572 case ITUNDRAFT:
573 rv = !(m->m_flag & MDRAFTED);
574 break;
575 case ITUNFLAGGED:
576 rv = !(m->m_flag & MFLAGGED);
577 break;
578 case ITUNKEYWORD:
579 rv = !(m->m_flag & n->n_n);
580 break;
581 case ITUNSEEN:
582 rv = !(m->m_flag & MREAD);
583 break;
585 jleave:
586 NYD_LEAVE;
587 return rv;
590 static time_t
591 _imap_read_date(char const *cp)
593 time_t t = (time_t)-1;
594 int year, month, day, i, tzdiff;
595 struct tm *tmptr;
596 char *xp, *yp;
597 NYD_ENTER;
599 if (*cp == '"')
600 ++cp;
601 day = strtol(cp, &xp, 10);
602 if (day <= 0 || day > 31 || *xp++ != '-')
603 goto jleave;
605 for (i = 0;;) {
606 if (!ascncasecmp(xp, month_names[i], 3))
607 break;
608 if (month_names[++i][0] == '\0')
609 goto jleave;
611 month = i + 1;
612 if (xp[3] != '-')
613 goto jleave;
614 year = strtol(xp + 4, &yp, 10);
615 if (year < 1970 || year > 2037 || PTRCMP(yp, !=, xp + 8))
616 goto jleave;
617 if (yp[0] != '\0' && (yp[1] != '"' || yp[2] != '\0'))
618 goto jleave;
619 if ((t = combinetime(year, month, day, 0, 0, 0)) == (time_t)-1)
620 goto jleave;
621 tzdiff = t - mktime(gmtime(&t));
622 tmptr = localtime(&t);
623 if (tmptr->tm_isdst > 0)
624 tzdiff += 3600;
625 t -= tzdiff;
626 jleave:
627 NYD_LEAVE;
628 return t;
631 static bool_t
632 matchfield(struct message *m, char const *field, void const *what)
634 struct str in, out;
635 bool_t rv = FAL0;
636 NYD_ENTER;
638 if ((in.s = hfieldX(field, m)) == NULL)
639 goto jleave;
641 in.l = strlen(in.s);
642 mime_fromhdr(&in, &out, TD_ICONV);
643 rv = substr(out.s, what);
644 free(out.s);
645 jleave:
646 NYD_LEAVE;
647 return rv;
650 static int
651 matchenvelope(struct message *m, char const *field, void const *what)
653 struct name *np;
654 char *cp;
655 int rv = 0;
656 NYD_ENTER;
658 if ((cp = hfieldX(field, m)) == NULL)
659 goto jleave;
661 for (np = lextract(cp, GFULL); np != NULL; np = np->n_flink) {
662 if (!substr(np->n_name, what) && !substr(mkenvelope(np), what))
663 continue;
664 rv = 1;
665 break;
668 jleave:
669 NYD_LEAVE;
670 return rv;
673 static char *
674 mkenvelope(struct name *np)
676 size_t epsize;
677 char *ep, *realnam = NULL, *sourceaddr = NULL, *localpart = NULL,
678 *domainpart = NULL, *cp, *rp, *xp, *ip;
679 struct str in, out;
680 int level = 0;
681 bool_t hadphrase = FAL0;
682 NYD_ENTER;
684 in.s = np->n_fullname;
685 in.l = strlen(in.s);
686 mime_fromhdr(&in, &out, TD_ICONV);
687 rp = ip = ac_alloc(strlen(out.s) + 1);
688 for (cp = out.s; *cp; cp++) {
689 switch (*cp) {
690 case '"':
691 while (*cp) {
692 if (*++cp == '"')
693 break;
694 if (cp[0] == '\\' && cp[1] != '\0')
695 ++cp;
696 *rp++ = *cp;
698 break;
699 case '<':
700 while (cp > out.s && blankchar(cp[-1]))
701 --cp;
702 rp = ip;
703 xp = out.s;
704 if (PTRCMP(xp, <, cp - 1) && *xp == '"' && cp[-1] == '"') {
705 ++xp;
706 --cp;
708 while (xp < cp)
709 *rp++ = *xp++;
710 hadphrase = TRU1;
711 goto jdone;
712 case '(':
713 if (level++)
714 goto jdfl;
715 if (!hadphrase)
716 rp = ip;
717 hadphrase = TRU1;
718 break;
719 case ')':
720 if (--level)
721 goto jdfl;
722 break;
723 case '\\':
724 if (level && cp[1] != '\0')
725 cp++;
726 goto jdfl;
727 default:
728 jdfl:
729 *rp++ = *cp;
732 jdone:
733 *rp = '\0';
734 if (hadphrase)
735 realnam = ip;
736 free(out.s);
737 localpart = savestr(np->n_name);
738 if ((cp = strrchr(localpart, '@')) != NULL) {
739 *cp = '\0';
740 domainpart = cp + 1;
743 ep = salloc(epsize = strlen(np->n_fullname) * 2 + 40);
744 snprintf(ep, epsize, "(%s %s %s %s)",
745 realnam ? imap_quotestr(realnam) : "NIL",
746 sourceaddr ? imap_quotestr(sourceaddr) : "NIL",
747 localpart ? imap_quotestr(localpart) : "NIL",
748 domainpart ? imap_quotestr(domainpart) : "NIL");
749 ac_free(ip);
750 NYD_LEAVE;
751 return ep;
754 #define SURROUNDING 16
755 static char const *
756 around(char const *cp)
758 static char ab[2 * SURROUNDING +1];
760 size_t i;
761 NYD_ENTER;
763 for (i = 0; i < SURROUNDING && cp > _it_begin; ++i)
764 --cp;
765 for (i = 0; i < sizeof(ab) -1; ++i)
766 ab[i] = *cp++;
767 ab[i] = '\0';
768 NYD_LEAVE;
769 return ab;
772 FL enum okay
773 imap_search(char const *spec, int f)
775 static char *lastspec;
777 char const *xp;
778 size_t i;
779 enum okay rv = STOP;
780 NYD_ENTER;
782 if (strcmp(spec, "()")) {
783 if (lastspec != NULL)
784 free(lastspec);
785 i = strlen(spec);
786 lastspec = sbufdup(spec, i);
787 } else if (lastspec == NULL) {
788 n_err(_("No last SEARCH criteria available\n"));
789 goto jleave;
791 spec =
792 _it_begin = lastspec;
794 _it_need_headers = FAL0;
795 #ifdef HAVE_IMAP
796 if ((rv = imap_search1(spec, f) == OKAY))
797 goto jleave;
798 #endif
800 if (itparse(spec, &xp, 0) == STOP)
801 goto jleave;
802 if (_it_tree == NULL) {
803 rv = OKAY;
804 goto jleave;
807 #ifdef HAVE_IMAP
808 if (mb.mb_type == MB_IMAP && _it_need_headers)
809 imap_getheaders(1, msgCount);
810 #endif
811 srelax_hold();
812 for (i = 0; UICMP(z, i, <, msgCount); ++i) {
813 if (message[i].m_flag & MHIDDEN)
814 continue;
815 if (f == MDELETED || !(message[i].m_flag & MDELETED)) {
816 size_t j = (int)(i + 1);
817 if (itexecute(&mb, message + i, j, _it_tree))
818 mark((int)j, f);
819 srelax();
822 srelax_rele();
824 rv = OKAY;
825 jleave:
826 NYD_LEAVE;
827 return rv;
829 #endif /* HAVE_IMAP_SEARCH */
831 /* s-it-mode */