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>.
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
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
88 static unsigned long inumber
;
89 static void *iargs
[2];
90 static int needheaders
;
97 { "ANSWERED", ITANSWERED
},
99 { "BEFORE", ITBEFORE
},
102 { "DELETED", ITDELETED
},
103 { "DRAFT", ITDRAFT
},
104 { "FLAGGED", ITFLAGGED
},
106 { "HEADER", ITHEADER
},
107 { "KEYWORD", ITKEYWORD
},
108 { "LARGER", ITLARGER
},
114 { "RECENT", ITRECENT
},
116 { "SENTBEFORE", ITSENTBEFORE
},
117 { "SENTON", ITSENTON
},
118 { "SENTSINCE", ITSENTSINCE
},
119 { "SINCE", ITSINCE
},
120 { "SMALLER", ITSMALLER
},
121 { "SUBJECT", ITSUBJECT
},
125 { "UNANSWERED", ITUNANSWERED
},
126 { "UNDELETED", ITUNDELETED
},
127 { "UNDRAFT", ITUNDRAFT
},
128 { "UNFLAGGED", ITUNFLAGGED
},
129 { "UNKEYWORD", ITUNKEYWORD
},
130 { "UNSEEN", ITUNSEEN
},
134 static struct itnode
{
143 static const char *begin
;
145 static enum okay
itparse(char const *spec
, char const **xp
, int sub
);
146 static enum okay
itscan(char const *spec
, char const **xp
);
147 static enum okay
itsplit(char const *spec
, char const **xp
);
148 static enum okay
itstring(void **tp
, char const *spec
, char const **xp
);
149 static int itexecute(struct mailbox
*mp
, struct message
*m
,
150 int c
, struct itnode
*n
);
151 static int matchfield(struct message
*m
, const char *field
, const char *what
);
152 static int matchenvelope(struct message
*m
, const char *field
,
154 static char *mkenvelope(struct name
*np
);
155 static int matchmsg(struct message
*m
, const char *what
, int withheader
);
156 static const char *around(const char *cp
);
159 imap_search(const char *spec
, int f
)
161 static char *lastspec
;
165 if (strcmp(spec
, "()")) {
167 lastspec
= sstrdup(spec
);
168 } else if (lastspec
== NULL
) {
169 fprintf(stderr
, "No last SEARCH criteria available.\n");
175 if (imap_search1(spec
, f
) == OKAY
)
179 if (itparse(spec
, &xp
, 0) == STOP
)
184 if (mb
.mb_type
== MB_IMAP
&& needheaders
)
185 imap_getheaders(1, msgCount
);
188 for (i
= 0; i
< msgCount
; i
++) {
189 if (message
[i
].m_flag
&MHIDDEN
)
191 if (f
== MDELETED
|| (message
[i
].m_flag
&MDELETED
) == 0) {
192 if (itexecute(&mb
, &message
[i
], i
+1, ittree
))
202 itparse(char const *spec
, char const **xp
, int sub
)
205 struct itnode n
, *z
, *_ittree
;
209 while ((ok
= itscan(spec
, xp
)) == OKAY
&& itoken
!= ITBAD
&&
212 memset(&n
, 0, sizeof n
);
227 fprintf(stderr
, "Excess in \")\".\n");
234 if (itparse(spec
, xp
, sub
+1) == STOP
)
237 if ((n
.n_x
= ittree
) == NULL
) {
239 "Criterion for NOT missing: >>> %s <<<\n",
246 /* <search-key1> <search-key2> */
248 if (itparse(spec
, xp
, sub
+1) == STOP
)
250 if ((n
.n_x
= ittree
) == NULL
) {
251 fprintf(stderr
, "First criterion for OR "
252 "missing: >>> %s <<<\n",
257 if (itparse(spec
, xp
, sub
+1) == STOP
)
260 if ((n
.n_y
= ittree
) == NULL
) {
261 fprintf(stderr
, "Second criterion for OR "
262 "missing: >>> %s <<<\n",
274 if (ittree
== NULL
) {
275 ittree
= salloc(sizeof *ittree
);
279 ittree
= salloc(sizeof *ittree
);
280 ittree
->n_token
= ITAND
;
282 ittree
->n_y
= salloc(sizeof*ittree
->n_y
);
285 if (sub
&& level
== 0)
292 itscan(char const *spec
, char const **xp
)
296 while (spacechar(*spec
))
308 while (spacechar(*spec
))
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
;
321 while (spacechar(*spec
&0377))
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
== ')') {
334 fprintf(stderr
, "Bad SEARCH criterion \"");
335 while (*spec
&& !spacechar(*spec
) &&
336 *spec
!= '(' && *spec
!= ')') {
337 putc(*spec
&0377, stderr
);
340 fprintf(stderr
, "\": >>> %s <<<\n", around(*xp
));
346 itsplit(char const *spec
, char const **xp
)
361 return itstring(&iargs
[0], spec
, xp
);
371 if (itstring(&iargs
[0], spec
, xp
) != OKAY
)
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
));
381 /* <field-name> <string> */
383 if (itstring(&iargs
[0], spec
, xp
) != OKAY
)
386 return itstring(&iargs
[1], spec
, xp
);
390 if (itstring(&iargs
[0], spec
, xp
) != OKAY
)
392 if (asccasecmp(iargs
[0], "\\Seen") == 0)
394 else if (asccasecmp(iargs
[0], "\\Deleted") == 0)
396 else if (asccasecmp(iargs
[0], "\\Recent") == 0)
398 else if (asccasecmp(iargs
[0], "\\Flagged") == 0)
400 else if (asccasecmp(iargs
[0], "\\Answered") == 0)
402 else if (asccasecmp(iargs
[0], "\\Draft") == 0)
410 if (itstring(&iargs
[0], spec
, xp
) != OKAY
)
412 inumber
= strtoul(iargs
[0], &cp
, 10);
413 if (spacechar(*cp
&0377) || *cp
== '\0')
415 fprintf(stderr
, "Invalid size: >>> %s <<<\n",
421 "Searching for UIDs is not supported: >>> %s <<<\n",
431 itstring(void **tp
, char const *spec
, char const **xp
)
436 while (spacechar(*spec
&0377))
438 if (*spec
== '\0' || *spec
== '(' || *spec
== ')') {
439 fprintf(stderr
, "Missing string argument: >>> %s <<<\n",
440 around(&(*xp
)[spec
- *xp
]));
443 ap
= *tp
= salloc(strlen(spec
) + 1);
446 if (inquote
&& **xp
== '\\')
448 else if (**xp
== '"')
450 else if (!inquote
&& (spacechar(**xp
&0377) ||
451 **xp
== '(' || **xp
== ')')) {
461 itexecute(struct mailbox
*mp
, struct message
*m
, int c
, struct itnode
*n
)
463 char *cp
, *line
= NULL
;
468 fprintf(stderr
, "Internal error: Empty node in SEARCH tree.\n");
471 switch (n
->n_token
) {
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
);
486 if ((cp
= hfield1("date", m
)) != NULL
)
487 m
->m_date
= rfctime(cp
);
492 switch (n
->n_token
) {
494 fprintf(stderr
, "Internal SEARCH error: Lost token %d\n",
498 return itexecute(mp
, m
, c
, n
->n_x
) &
499 itexecute(mp
, m
, c
, n
->n_y
);
501 return (unsigned long)c
== n
->n_n
;
505 return (m
->m_flag
&MANSWERED
) != 0;
507 return matchenvelope(m
, "bcc", n
->n_v
);
509 return (unsigned long)m
->m_time
< n
->n_n
;
511 return matchmsg(m
, n
->n_v
, 0);
513 return matchenvelope(m
, "cc", n
->n_v
);
515 return (m
->m_flag
&MDELETED
) != 0;
517 return (m
->m_flag
&MDRAFTED
) != 0;
519 return (m
->m_flag
&MFLAGGED
) != 0;
521 return matchenvelope(m
, "from", n
->n_v
);
523 return matchfield(m
, n
->n_v
, n
->n_w
);
525 return (m
->m_flag
& n
->n_n
) != 0;
527 return m
->m_xsize
> n
->n_n
;
529 return (m
->m_flag
&(MNEW
|MREAD
)) == MNEW
;
531 return !itexecute(mp
, m
, c
, n
->n_x
);
533 return (m
->m_flag
&MNEW
) == 0;
535 return ((unsigned long)m
->m_time
>= n
->n_n
&&
536 (unsigned long)m
->m_time
< n
->n_n
+ 86400);
538 return itexecute(mp
, m
, c
, n
->n_x
) |
539 itexecute(mp
, m
, c
, n
->n_y
);
541 return (m
->m_flag
&MNEW
) != 0;
543 return (m
->m_flag
&MREAD
) != 0;
545 return (unsigned long)m
->m_date
< n
->n_n
;
547 return ((unsigned long)m
->m_date
>= n
->n_n
&&
548 (unsigned long)m
->m_date
< n
->n_n
+ 86400);
550 return (unsigned long)m
->m_date
>= n
->n_n
;
552 return (unsigned long)m
->m_time
>= n
->n_n
;
554 return (unsigned long)m
->m_xsize
< n
->n_n
;
556 return matchfield(m
, "subject", n
->n_v
);
558 return matchmsg(m
, n
->n_v
, 1);
560 return matchenvelope(m
, "to", n
->n_v
);
562 return (m
->m_flag
&MANSWERED
) == 0;
564 return (m
->m_flag
&MDELETED
) == 0;
566 return (m
->m_flag
&MDRAFTED
) == 0;
568 return (m
->m_flag
&MFLAGGED
) == 0;
570 return (m
->m_flag
& n
->n_n
) == 0;
572 return (m
->m_flag
&MREAD
) == 0;
577 matchfield(struct message
*m
, const char *field
, const char *what
)
582 if ((in
.s
= hfieldX(imap_unquotestr(field
), m
)) == NULL
)
585 mime_fromhdr(&in
, &out
, TD_ICONV
);
586 what
= imap_unquotestr(what
);
587 i
= substr(out
.s
, what
);
593 matchenvelope(struct message
*m
, const char *field
, const char *what
)
598 if ((cp
= hfieldX(imap_unquotestr(field
), m
)) == NULL
)
600 what
= imap_unquotestr(what
);
601 np
= lextract(cp
, GFULL
);
603 if (substr(np
->n_name
, what
))
605 if (substr(mkenvelope(np
), what
))
613 mkenvelope(struct name
*np
)
617 char *realnam
= NULL
, *sourceaddr
= NULL
,
618 *localpart
= NULL
, *domainpart
= NULL
,
621 int level
= 0, hadphrase
= 0;
623 in
.s
= np
->n_fullname
;
625 mime_fromhdr(&in
, &out
, TD_ICONV
);
626 rp
= ip
= ac_alloc(strlen(out
.s
) + 1);
627 for (cp
= out
.s
; *cp
; cp
++) {
633 if (*cp
== '\\' && cp
[1])
639 while (cp
> out
.s
&& blankchar(cp
[-1]&0377))
643 if (xp
< &cp
[-1] && *xp
== '"' && cp
[-1] == '"') {
654 if (hadphrase
++ == 0)
674 localpart
= savestr(np
->n_name
);
675 if ((cp
= strrchr(localpart
, '@')) != NULL
) {
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");
690 matchmsg(struct message
*m
, const char *what
, int withheader
)
692 char *tempFile
, *line
= NULL
;
693 size_t linesize
, linelen
, cnt
;
697 if ((fp
= Ftemp(&tempFile
, "Ra", "w+", 0600, 1)) == NULL
)
701 if (sendmp(m
, fp
, NULL
, NULL
, SEND_TOSRCH
, NULL
) < 0)
706 line
= smalloc(linesize
= LINESIZE
);
709 while (fgetline(&line
, &linesize
, &cnt
, &linelen
, fp
, 0))
712 what
= imap_unquotestr(what
);
713 while (fgetline(&line
, &linesize
, &cnt
, &linelen
, fp
, 0))
714 if (substr(line
, what
)) {
724 #define SURROUNDING 16
726 around(const char *cp
)
729 static char ab
[2*SURROUNDING
+1];
731 for (i
= 0; i
< SURROUNDING
&& cp
> begin
; i
++)
733 for (i
= 0; i
< (int)sizeof ab
- 1; i
++)