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