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.
5 *
6 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
7 * Copyright (c) 2012 - 2020 Steffen (Daode) Nurpmeso <steffen@sdaoden.eu>.
8 * SPDX-License-Identifier: BSD-4-Clause
9 */
10 /*
11 * Copyright (c) 2004
12 * Gunnar Ritter. All rights reserved.
13 *
14 * Redistribution and use in source and binary forms, with or without
15 * modification, are permitted provided that the following conditions
16 * are met:
17 * 1. Redistributions of source code must retain the above copyright
18 * notice, this list of conditions and the following disclaimer.
19 * 2. Redistributions in binary form must reproduce the above copyright
20 * notice, this list of conditions and the following disclaimer in the
21 * documentation and/or other materials provided with the distribution.
22 * 3. All advertising materials mentioning features or use of this software
23 * must display the following acknowledgement:
24 * This product includes software developed by Gunnar Ritter
25 * and his contributors.
26 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
27 * may be used to endorse or promote products derived from this software
28 * without specific prior written permission.
29 *
30 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``AS IS'' AND
31 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
32 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33 * ARE DISCLAIMED. IN NO EVENT SHALL GUNNAR RITTER OR CONTRIBUTORS BE LIABLE
34 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
35 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
36 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
37 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
38 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
39 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
40 * SUCH DAMAGE.
41 */
42 #undef su_FILE
43 #define su_FILE imap_search
44 #define mx_SOURCE
45
46 #ifndef mx_HAVE_AMALGAMATION
47 # include "mx/nail.h"
48 #endif
49
50 su_EMPTY_FILE()
51 #ifdef mx_HAVE_IMAP_SEARCH
52
53 #include <su/cs.h>
54 #include <su/icodec.h>
55 #include <su/mem.h>
56
57 #include "mx/file-streams.h"
58 #include "mx/names.h"
59
60 /* TODO fake */
61 #include "su/code-in.h"
62
63 enum itoken {
64 ITBAD, ITEOD, ITBOL, ITEOL, ITAND, ITSET, ITALL, ITANSWERED,
65 ITBCC, ITBEFORE, ITBODY,
66 ITCC,
67 ITDELETED, ITDRAFT,
68 ITFLAGGED, ITFROM,
69 ITHEADER,
70 ITKEYWORD,
71 ITLARGER,
72 ITNEW, ITNOT,
73 ITOLD, ITON, ITOR,
74 ITRECENT,
75 ITSEEN, ITSENTBEFORE, ITSENTON, ITSENTSINCE, ITSINCE, ITSMALLER,
76 ITSUBJECT,
77 ITTEXT, ITTO,
78 ITUID, ITUNANSWERED, ITUNDELETED, ITUNDRAFT, ITUNFLAGGED, ITUNKEYWORD,
79 ITUNSEEN
80 };
81
82 struct itlex {
83 char const *s_string;
84 enum itoken s_token;
85 };
86
87 struct itnode {
88 enum itoken n_token;
89 uz n_n;
90 void *n_v;
91 void *n_w;
92 struct itnode *n_x;
93 struct itnode *n_y;
94 };
95
96 static struct itlex const _it_strings[] = {
97 { "ALL", ITALL },
98 { "ANSWERED", ITANSWERED },
99 { "BCC", ITBCC },
100 { "BEFORE", ITBEFORE },
101 { "BODY", ITBODY },
102 { "CC", ITCC },
103 { "DELETED", ITDELETED },
104 { "DRAFT", ITDRAFT },
105 { "FLAGGED", ITFLAGGED },
106 { "FROM", ITFROM },
107 { "HEADER", ITHEADER },
108 { "KEYWORD", ITKEYWORD },
109 { "LARGER", ITLARGER },
110 { "NEW", ITNEW },
111 { "NOT", ITNOT },
112 { "OLD", ITOLD },
113 { "ON", ITON },
114 { "OR", ITOR },
115 { "RECENT", ITRECENT },
116 { "SEEN", ITSEEN },
117 { "SENTBEFORE", ITSENTBEFORE },
118 { "SENTON", ITSENTON },
119 { "SENTSINCE", ITSENTSINCE },
120 { "SINCE", ITSINCE },
121 { "SMALLER", ITSMALLER },
122 { "SUBJECT", ITSUBJECT },
123 { "TEXT", ITTEXT },
124 { "TO", ITTO },
125 { "UID", ITUID },
126 { "UNANSWERED", ITUNANSWERED },
127 { "UNDELETED", ITUNDELETED },
128 { "UNDRAFT", ITUNDRAFT },
129 { "UNFLAGGED", ITUNFLAGGED },
130 { "UNKEYWORD", ITUNKEYWORD },
131 { "UNSEEN", ITUNSEEN },
132 { NULL, ITBAD }
133 };
134
135 static struct itnode *_it_tree;
136 static char *_it_begin;
137 static enum itoken _it_token;
138 static uz _it_number;
139 static void *_it_args[2];
140 static uz _it_need_headers;
141
142 static enum okay itparse(char const *spec, char const **xp, int sub);
143 static enum okay itscan(char const *spec, char const **xp);
144 static enum okay itsplit(char const *spec, char const **xp);
145 static enum okay itstring(void **tp, char const *spec, char const **xp);
146 static int itexecute(struct mailbox *mp, struct message *m,
147 uz c, struct itnode *n);
148
149 static time_t _imap_read_date(char const *cp);
150 static char * _imap_quotestr(char const *s);
151 static char * _imap_unquotestr(char const *s);
152
153 static boole matchfield(struct message *m, char const *field,
154 void const *what);
155 static int matchenvelope(struct message *m, char const *field,
156 void const *what);
157 static char * mkenvelope(struct mx_name *np);
158 static char const * around(char const *cp);
159
160 static enum okay
itparse(char const * spec,char const ** xp,int sub)161 itparse(char const *spec, char const **xp, int sub)
162 {
163 int level = 0;
164 struct itnode n, *z, *ittree;
165 enum okay rv;
166 NYD_IN;
167
168 _it_tree = NULL;
169 while ((rv = itscan(spec, xp)) == OKAY && _it_token != ITBAD &&
170 _it_token != ITEOD) {
171 ittree = _it_tree;
172 su_mem_set(&n, 0, sizeof n);
173 spec = *xp;
174 switch (_it_token) {
175 case ITBOL:
176 ++level;
177 continue;
178 case ITEOL:
179 if (--level == 0)
180 goto jleave;
181 if (level < 0) {
182 if (sub > 0) {
183 --(*xp);
184 goto jleave;
185 }
186 n_err(_("Excess in )\n"));
187 rv = STOP;
188 goto jleave;
189 }
190 continue;
191 case ITNOT:
192 /* <search-key> */
193 n.n_token = ITNOT;
194 if ((rv = itparse(spec, xp, sub + 1)) == STOP)
195 goto jleave;
196 spec = *xp;
197 if ((n.n_x = _it_tree) == NULL) {
198 n_err(_("Criterion for NOT missing: >>> %s <<<\n"), around(*xp));
199 rv = STOP;
200 goto jleave;
201 }
202 _it_token = ITNOT;
203 break;
204 case ITOR:
205 /* <search-key1> <search-key2> */
206 n.n_token = ITOR;
207 if ((rv = itparse(spec, xp, sub + 1)) == STOP)
208 goto jleave;
209 if ((n.n_x = _it_tree) == NULL) {
210 n_err(_("First criterion for OR missing: >>> %s <<<\n"),
211 around(*xp));
212 rv = STOP;
213 goto jleave;
214 }
215 spec = *xp;
216 if ((rv = itparse(spec, xp, sub + 1)) == STOP)
217 goto jleave;
218 spec = *xp;
219 if ((n.n_y = _it_tree) == NULL) {
220 n_err(_("Second criterion for OR missing: >>> %s <<<\n"),
221 around(*xp));
222 rv = STOP;
223 goto jleave;
224 }
225 break;
226 default:
227 n.n_token = _it_token;
228 n.n_n = _it_number;
229 n.n_v = _it_args[0];
230 n.n_w = _it_args[1];
231 }
232
233 _it_tree = ittree;
234 if (_it_tree == NULL) {
235 _it_tree = n_autorec_alloc(sizeof *_it_tree);
236 *_it_tree = n;
237 } else {
238 z = _it_tree;
239 _it_tree = n_autorec_alloc(sizeof *_it_tree);
240 _it_tree->n_token = ITAND;
241 _it_tree->n_x = z;
242 _it_tree->n_y = n_autorec_alloc(sizeof *_it_tree->n_y);
243 *_it_tree->n_y = n;
244 }
245 if (sub && level == 0)
246 break;
247 }
248 jleave:
249 NYD_OU;
250 return rv;
251 }
252
253 static enum okay
itscan(char const * spec,char const ** xp)254 itscan(char const *spec, char const **xp)
255 {
256 int i, n;
257 enum okay rv = OKAY;
258 NYD_IN;
259
260 while (su_cs_is_space(*spec))
261 ++spec;
262 if (*spec == '(') {
263 *xp = &spec[1];
264 _it_token = ITBOL;
265 goto jleave;
266 }
267 if (*spec == ')') {
268 *xp = &spec[1];
269 _it_token = ITEOL;
270 goto jleave;
271 }
272 while (su_cs_is_space(*spec))
273 ++spec;
274 if (*spec == '\0') {
275 _it_token = ITEOD;
276 goto jleave;
277 }
278
279 #define __GO(C) ((C) != '\0' && (C) != '(' && (C) != ')' && !su_cs_is_space(C))
280 for (i = 0; _it_strings[i].s_string != NULL; ++i) {
281 n = su_cs_len(_it_strings[i].s_string);
282 if (!su_cs_cmp_case_n(spec, _it_strings[i].s_string, n) &&
283 !__GO(spec[n])) {
284 _it_token = _it_strings[i].s_token;
285 spec += n;
286 while (su_cs_is_space(*spec))
287 ++spec;
288 rv = itsplit(spec, xp);
289 goto jleave;
290 }
291 }
292 if (su_cs_is_digit(*spec)) {
293 su_idec_uz_cp(&_it_number, spec, 10, xp);
294 if (!__GO(**xp)) {
295 _it_token = ITSET;
296 goto jleave;
297 }
298 }
299
300 n_err(_("Bad SEARCH criterion: "));
301 for (i = 0; __GO(spec[i]); ++i)
302 ;
303 n_err(_("%.*s: >>> %s <<<\n"), i, spec, around(*xp));
304 #undef __GO
305
306 _it_token = ITBAD;
307 rv = STOP;
308 jleave:
309 NYD_OU;
310 return rv;
311 }
312
313 static enum okay
itsplit(char const * spec,char const ** xp)314 itsplit(char const *spec, char const **xp)
315 {
316 char const *cp;
317 time_t t;
318 enum okay rv;
319 NYD_IN;
320
321 switch (_it_token) {
322 case ITBCC:
323 case ITBODY:
324 case ITCC:
325 case ITFROM:
326 case ITSUBJECT:
327 case ITTEXT:
328 case ITTO:
329 /* <string> */
330 ++_it_need_headers;
331 rv = itstring(_it_args, spec, xp);
332 break;
333 case ITSENTBEFORE:
334 case ITSENTON:
335 case ITSENTSINCE:
336 ++_it_need_headers;
337 /*FALLTHRU*/
338 case ITBEFORE:
339 case ITON:
340 case ITSINCE:
341 /* <date> */
342 if ((rv = itstring(_it_args, spec, xp)) != OKAY)
343 break;
344 if ((t = _imap_read_date(_it_args[0])) == (time_t)-1) {
345 n_err(_("Invalid date %s: >>> %s <<<\n"),
346 (char*)_it_args[0], around(*xp));
347 rv = STOP;
348 break;
349 }
350 _it_number = t;
351 rv = OKAY;
352 break;
353 case ITHEADER:
354 /* <field-name> <string> */
355 ++_it_need_headers;
356 if ((rv = itstring(_it_args, spec, xp)) != OKAY)
357 break;
358 spec = *xp;
359 if ((rv = itstring(_it_args + 1, spec, xp)) != OKAY)
360 break;
361 break;
362 case ITKEYWORD:
363 case ITUNKEYWORD:
364 /* <flag> */ /* TODO use table->flag map search instead */
365 if ((rv = itstring(_it_args, spec, xp)) != OKAY)
366 break;
367 if (!su_cs_cmp_case(_it_args[0], "\\Seen"))
368 _it_number = MREAD;
369 else if (!su_cs_cmp_case(_it_args[0], "\\Deleted"))
370 _it_number = MDELETED;
371 else if (!su_cs_cmp_case(_it_args[0], "\\Recent"))
372 _it_number = MNEW;
373 else if (!su_cs_cmp_case(_it_args[0], "\\Flagged"))
374 _it_number = MFLAGGED;
375 else if (!su_cs_cmp_case(_it_args[0], "\\Answered"))
376 _it_number = MANSWERED;
377 else if (!su_cs_cmp_case(_it_args[0], "\\Draft"))
378 _it_number = MDRAFT;
379 else
380 _it_number = 0;
381 break;
382 case ITLARGER:
383 case ITSMALLER:
384 /* <n> */
385 if ((rv = itstring(_it_args, spec, xp)) != OKAY)
386 break;
387 else{
388 su_idec_uz_cp(&_it_number, _it_args[0], 10, &cp);
389 }
390 if (su_cs_is_space(*cp) || *cp == '\0')
391 break;
392 n_err(_("Invalid size: >>> %s <<<\n"), around(*xp));
393 rv = STOP;
394 break;
395 case ITUID:
396 /* <message set> */
397 n_err(_("Searching for UIDs is not supported: >>> %s <<<\n"),
398 around(*xp));
399 rv = STOP;
400 break;
401 default:
402 *xp = spec;
403 rv = OKAY;
404 break;
405 }
406 NYD_OU;
407 return rv;
408 }
409
410 static enum okay
itstring(void ** tp,char const * spec,char const ** xp)411 itstring(void **tp, char const *spec, char const **xp) /* XXX lesser derefs */
412 {
413 int inquote = 0;
414 char *ap;
415 enum okay rv = STOP;
416 NYD_IN;
417
418 while (su_cs_is_space(*spec))
419 ++spec;
420 if (*spec == '\0' || *spec == '(' || *spec == ')') {
421 n_err(_("Missing string argument: >>> %s <<<\n"),
422 around(&(*xp)[spec - *xp]));
423 goto jleave;
424 }
425 ap = *tp = n_autorec_alloc(su_cs_len(spec) +1);
426 *xp = spec;
427 do {
428 if (inquote && **xp == '\\')
429 *ap++ = *(*xp)++;
430 else if (**xp == '"')
431 inquote = !inquote;
432 else if (!inquote &&
433 (su_cs_is_space(**xp) || **xp == '(' || **xp == ')')) {
434 *ap++ = '\0';
435 break;
436 }
437 *ap++ = **xp;
438 } while (*(*xp)++);
439
440 *tp = _imap_unquotestr(*tp);
441 rv = OKAY;
442 jleave:
443 NYD_OU;
444 return rv;
445 }
446
447 static int
itexecute(struct mailbox * mp,struct message * m,uz c,struct itnode * n)448 itexecute(struct mailbox *mp, struct message *m, uz c, struct itnode *n)
449 {
450 struct search_expr se;
451 char *cp;
452 FILE *ibuf;
453 int rv;
454 NYD_IN;
455
456 if (n == NULL) {
457 n_err(_("Internal error: Empty node in SEARCH tree\n"));
458 rv = 0;
459 goto jleave;
460 }
461
462 switch (n->n_token) {
463 case ITBEFORE:
464 case ITON:
465 case ITSINCE:
466 if (m->m_time == 0 && !(m->m_flag & MNOFROM) &&
467 (ibuf = setinput(mp, m, NEED_HEADER)) != NULL) {
468 char *line;
469 uz linesize;
470
471 mx_fs_linepool_aquire(&line, &linesize);
472 if (readline_restart(ibuf, &line, &linesize, 0) > 0)
473 m->m_time = unixtime(line);
474 mx_fs_linepool_release(line, linesize);
475 }
476 break;
477 case ITSENTBEFORE:
478 case ITSENTON:
479 case ITSENTSINCE:
480 if (m->m_date == 0)
481 if ((cp = hfield1("date", m)) != NULL)
482 m->m_date = rfctime(cp);
483 break;
484 default:
485 break;
486 }
487
488 switch (n->n_token) {
489 default:
490 n_err(_("Internal SEARCH error: Lost token %d\n"), n->n_token);
491 rv = 0;
492 break;
493 case ITAND:
494 rv = itexecute(mp, m, c, n->n_x) & itexecute(mp, m, c, n->n_y);
495 break;
496 case ITSET:
497 rv = (c == n->n_n);
498 break;
499 case ITALL:
500 rv = 1;
501 break;
502 case ITANSWERED:
503 rv = ((m->m_flag & MANSWERED) != 0);
504 break;
505 case ITBCC:
506 rv = matchenvelope(m, "bcc", n->n_v);
507 break;
508 case ITBEFORE:
509 rv = UCMP(z, m->m_time, <, n->n_n);
510 break;
511 case ITBODY:
512 su_mem_set(&se, 0, sizeof se);
513 se.ss_field = "body";
514 se.ss_body = n->n_v;
515 rv = message_match(m, &se, FAL0);
516 break;
517 case ITCC:
518 rv = matchenvelope(m, "cc", n->n_v);
519 break;
520 case ITDELETED:
521 rv = ((m->m_flag & MDELETED) != 0);
522 break;
523 case ITDRAFT:
524 rv = ((m->m_flag & MDRAFTED) != 0);
525 break;
526 case ITFLAGGED:
527 rv = ((m->m_flag & MFLAGGED) != 0);
528 break;
529 case ITFROM:
530 rv = matchenvelope(m, "from", n->n_v);
531 break;
532 case ITHEADER:
533 rv = matchfield(m, n->n_v, n->n_w);
534 break;
535 case ITKEYWORD:
536 rv = ((m->m_flag & n->n_n) != 0);
537 break;
538 case ITLARGER:
539 rv = (m->m_xsize > n->n_n);
540 break;
541 case ITNEW:
542 rv = ((m->m_flag & (MNEW | MREAD)) == MNEW);
543 break;
544 case ITNOT:
545 rv = !itexecute(mp, m, c, n->n_x);
546 break;
547 case ITOLD:
548 rv = !(m->m_flag & MNEW);
549 break;
550 case ITON:
551 rv = (UCMP(z, m->m_time, >=, n->n_n) &&
552 UCMP(z, m->m_time, <, n->n_n + 86400));
553 break;
554 case ITOR:
555 rv = itexecute(mp, m, c, n->n_x) | itexecute(mp, m, c, n->n_y);
556 break;
557 case ITRECENT:
558 rv = ((m->m_flag & MNEW) != 0);
559 break;
560 case ITSEEN:
561 rv = ((m->m_flag & MREAD) != 0);
562 break;
563 case ITSENTBEFORE:
564 rv = UCMP(z, m->m_date, <, n->n_n);
565 break;
566 case ITSENTON:
567 rv = (UCMP(z, m->m_date, >=, n->n_n) &&
568 UCMP(z, m->m_date, <, n->n_n + 86400));
569 break;
570 case ITSENTSINCE:
571 rv = UCMP(z, m->m_date, >=, n->n_n);
572 break;
573 case ITSINCE:
574 rv = UCMP(z, m->m_time, >=, n->n_n);
575 break;
576 case ITSMALLER:
577 rv = UCMP(z, m->m_xsize, <, n->n_n);
578 break;
579 case ITSUBJECT:
580 rv = matchfield(m, "subject", n->n_v);
581 break;
582 case ITTEXT:
583 su_mem_set(&se, 0, sizeof se);
584 se.ss_field = "text";
585 se.ss_body = n->n_v;
586 rv = message_match(m, &se, TRU1);
587 break;
588 case ITTO:
589 rv = matchenvelope(m, "to", n->n_v);
590 break;
591 case ITUNANSWERED:
592 rv = !(m->m_flag & MANSWERED);
593 break;
594 case ITUNDELETED:
595 rv = !(m->m_flag & MDELETED);
596 break;
597 case ITUNDRAFT:
598 rv = !(m->m_flag & MDRAFTED);
599 break;
600 case ITUNFLAGGED:
601 rv = !(m->m_flag & MFLAGGED);
602 break;
603 case ITUNKEYWORD:
604 rv = !(m->m_flag & n->n_n);
605 break;
606 case ITUNSEEN:
607 rv = !(m->m_flag & MREAD);
608 break;
609 }
610 jleave:
611 NYD_OU;
612 return rv;
613 }
614
615 static time_t
_imap_read_date(char const * cp)616 _imap_read_date(char const *cp)
617 {
618 time_t t;
619 s32 year, month, day, i;
620 char const *xp, *yp;
621 NYD_IN;
622
623 if (*cp == '"')
624 ++cp;
625 su_idec_s32_cp(&day, cp, 10, &xp);
626 if (day <= 0 || day > 31 || *xp++ != '-')
627 goto jerr;
628
629 for (i = 0;;) {
630 if (!su_cs_cmp_case_n(xp, n_month_names[i], 3))
631 break;
632 if (n_month_names[++i][0] == '\0')
633 goto jerr;
634 }
635 month = i + 1;
636 if (xp[3] != '-')
637 goto jerr;
638 su_idec_s32_cp(&year, &xp[4], 10, &yp);
639 if (year < 1970 || year > 2037 || PCMP(yp, !=, xp + 8))
640 goto jerr;
641 if (yp[0] != '\0' && (yp[1] != '"' || yp[2] != '\0'))
642 goto jerr;
643
644 if((t = combinetime(year, month, day, 0, 0, 0)) == (time_t)-1)
645 goto jleave/*jerr*/;
646
647 t += n_time_tzdiff(t, NIL, NIL);
648
649 jleave:
650 NYD_OU;
651 return t;
652 jerr:
653 t = (time_t)-1;
654 goto jleave;
655 }
656
657 static char *
_imap_quotestr(char const * s)658 _imap_quotestr(char const *s)
659 {
660 char *n, *np;
661 NYD2_IN;
662
663 np = n = n_autorec_alloc(2 * su_cs_len(s) + 3);
664 *np++ = '"';
665 while (*s) {
666 if (*s == '"' || *s == '\\')
667 *np++ = '\\';
668 *np++ = *s++;
669 }
670 *np++ = '"';
671 *np = '\0';
672 NYD2_OU;
673 return n;
674 }
675
676 static char *
_imap_unquotestr(char const * s)677 _imap_unquotestr(char const *s)
678 {
679 char *n, *np;
680 NYD2_IN;
681
682 if (*s != '"') {
683 n = savestr(s);
684 goto jleave;
685 }
686
687 np = n = n_autorec_alloc(su_cs_len(s) + 1);
688 while (*++s) {
689 if (*s == '\\')
690 s++;
691 else if (*s == '"')
692 break;
693 *np++ = *s;
694 }
695 *np = '\0';
696 jleave:
697 NYD2_OU;
698 return n;
699 }
700
701 static boole
matchfield(struct message * m,char const * field,void const * what)702 matchfield(struct message *m, char const *field, void const *what)
703 {
704 struct str in, out;
705 boole rv = FAL0;
706 NYD_IN;
707
708 if ((in.s = hfieldX(field, m)) == NULL)
709 goto jleave;
710
711 in.l = su_cs_len(in.s);
712 mime_fromhdr(&in, &out, TD_ICONV);
713 rv = substr(out.s, what);
714 n_free(out.s);
715 jleave:
716 NYD_OU;
717 return rv;
718 }
719
720 static int
matchenvelope(struct message * m,char const * field,void const * what)721 matchenvelope(struct message *m, char const *field, void const *what)
722 {
723 struct mx_name *np;
724 char *cp;
725 int rv = 0;
726 NYD_IN;
727
728 if ((cp = hfieldX(field, m)) == NULL)
729 goto jleave;
730
731 for (np = lextract(cp, GFULL); np != NULL; np = np->n_flink) {
732 if (!substr(np->n_name, what) && !substr(mkenvelope(np), what))
733 continue;
734 rv = 1;
735 break;
736 }
737
738 jleave:
739 NYD_OU;
740 return rv;
741 }
742
743 static char *
mkenvelope(struct mx_name * np)744 mkenvelope(struct mx_name *np)
745 {
746 uz epsize;
747 char *ep, *realnam = NULL, /**sourceaddr = NULL,*/ *localpart,
748 *domainpart, *cp, *rp, *xp, *ip;
749 struct str in, out;
750 int level = 0;
751 boole hadphrase = FAL0;
752 NYD_IN;
753
754 in.s = np->n_fullname;
755 in.l = su_cs_len(in.s);
756 mime_fromhdr(&in, &out, TD_ICONV);
757 rp = ip = n_lofi_alloc(su_cs_len(out.s) + 1);
758 for (cp = out.s; *cp; cp++) {
759 switch (*cp) {
760 case '"':
761 while (*cp) {
762 if (*++cp == '"')
763 break;
764 if (cp[0] == '\\' && cp[1] != '\0')
765 ++cp;
766 *rp++ = *cp;
767 }
768 break;
769 case '<':
770 while (cp > out.s && su_cs_is_blank(cp[-1]))
771 --cp;
772 rp = ip;
773 xp = out.s;
774 if (PCMP(xp, <, cp - 1) && *xp == '"' && cp[-1] == '"') {
775 ++xp;
776 --cp;
777 }
778 while (xp < cp)
779 *rp++ = *xp++;
780 hadphrase = TRU1;
781 goto jdone;
782 case '(':
783 if (level++)
784 goto jdfl;
785 if (!hadphrase)
786 rp = ip;
787 hadphrase = TRU1;
788 break;
789 case ')':
790 if (--level)
791 goto jdfl;
792 break;
793 case '\\':
794 if (level && cp[1] != '\0')
795 cp++;
796 goto jdfl;
797 default:
798 jdfl:
799 *rp++ = *cp;
800 }
801 }
802 jdone:
803 *rp = '\0';
804 if (hadphrase)
805 realnam = ip;
806 n_free(out.s);
807 localpart = savestr(np->n_name);
808 if ((cp = su_cs_rfind_c(localpart, '@')) != NULL) {
809 *cp = '\0';
810 domainpart = cp + 1;
811 }else
812 domainpart = NULL;
813
814 ep = n_autorec_alloc(epsize = su_cs_len(np->n_fullname) * 2 + 40);
815 snprintf(ep, epsize, "(%s %s %s %s)",
816 realnam ? _imap_quotestr(realnam) : "NIL",
817 /*sourceaddr ? _imap_quotestr(sourceaddr) :*/ "NIL",
818 _imap_quotestr(localpart),
819 domainpart ? _imap_quotestr(domainpart) : "NIL");
820 n_lofi_free(ip);
821 NYD_OU;
822 return ep;
823 }
824
825 #define SURROUNDING 16
826 static char const *
around(char const * cp)827 around(char const *cp)
828 {
829 static char ab[2 * SURROUNDING +1];
830
831 uz i;
832 NYD_IN;
833
834 for (i = 0; i < SURROUNDING && cp > _it_begin; ++i)
835 --cp;
836 for (i = 0; i < sizeof(ab) -1; ++i)
837 ab[i] = *cp++;
838 ab[i] = '\0';
839 NYD_OU;
840 return ab;
841 }
842
843 FL sz
imap_search(char const * spec,int f)844 imap_search(char const *spec, int f)
845 {
846 static char *lastspec;
847
848 char const *xp;
849 uz i;
850 sz rv;
851 NYD_IN;
852
853 if (su_cs_cmp(spec, "()")) {
854 if (lastspec != NULL)
855 n_free(lastspec);
856 i = su_cs_len(spec);
857 lastspec = su_cs_dup_cbuf(spec, i, 0);
858 } else if (lastspec == NULL) {
859 n_err(_("No last SEARCH criteria available\n"));
860 rv = -1;
861 goto jleave;
862 }
863 spec =
864 _it_begin = lastspec;
865
866 _it_need_headers = FAL0;
867 #ifdef mx_HAVE_IMAP
868 if ((rv = imap_search1(spec, f) == OKAY))
869 goto jleave;
870 #endif
871 if (itparse(spec, &xp, 0) == STOP){
872 rv = -1;
873 goto jleave;
874 }
875
876 rv = 0;
877
878 if (_it_tree == NULL)
879 goto jleave;
880
881 #ifdef mx_HAVE_IMAP
882 if (mb.mb_type == MB_IMAP && _it_need_headers)
883 imap_getheaders(1, msgCount);
884 #endif
885 srelax_hold();
886 for (i = 0; UCMP(z, i, <, msgCount); ++i) {
887 if (message[i].m_flag & MHIDDEN)
888 continue;
889 if (f == MDELETED || !(message[i].m_flag & MDELETED)) {
890 uz j = (int)(i + 1);
891 if (itexecute(&mb, message + i, j, _it_tree)){
892 mark((int)j, f);
893 ++rv;
894 }
895 srelax();
896 }
897 }
898 srelax_rele();
899 jleave:
900 NYD_OU;
901 return rv;
902 }
903
904 #include "su/code-ou.h"
905 #endif /* mx_HAVE_IMAP_SEARCH */
906 /* s-it-mode */
907