2 * Heirloom mailx - a mail user agent derived from Berkeley Mail.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
5 * Copyright (c) 2012 Steffen "Daode" Nurpmeso.
9 * Gunnar Ritter. All rights reserved.
11 * Redistribution and use in source and binary forms, with or without
12 * modification, are permitted provided that the following conditions
14 * 1. Redistributions of source code must retain the above copyright
15 * notice, this list of conditions and the following disclaimer.
16 * 2. Redistributions in binary form must reproduce the above copyright
17 * notice, this list of conditions and the following disclaimer in the
18 * documentation and/or other materials provided with the distribution.
19 * 3. All advertising materials mentioning features or use of this software
20 * must display the following acknowledgement:
21 * This product includes software developed by Gunnar Ritter
22 * and his contributors.
23 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
24 * may be used to endorse or promote products derived from this software
25 * without specific prior written permission.
27 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``AS IS'' AND
28 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
29 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL GUNNAR RITTER OR CONTRIBUTORS BE LIABLE
31 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
32 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
33 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
34 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
35 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
36 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
42 static char sccsid
[] = "@(#)imap_search.c 1.29 (gritter) 3/4/06";
53 * Mail -- a mail program
55 * Client-side implementation of the IMAP SEARCH command. This is used
56 * for folders not located on IMAP servers, or for IMAP servers that do
57 * not implement the SEARCH command.
104 static unsigned long inumber
;
105 static void *iargs
[2];
106 static int needheaders
;
108 static struct itlex
{
109 const char *s_string
;
113 { "ANSWERED", ITANSWERED
},
115 { "BEFORE", ITBEFORE
},
118 { "DELETED", ITDELETED
},
119 { "DRAFT", ITDRAFT
},
120 { "FLAGGED", ITFLAGGED
},
122 { "HEADER", ITHEADER
},
123 { "KEYWORD", ITKEYWORD
},
124 { "LARGER", ITLARGER
},
130 { "RECENT", ITRECENT
},
132 { "SENTBEFORE", ITSENTBEFORE
},
133 { "SENTON", ITSENTON
},
134 { "SENTSINCE", ITSENTSINCE
},
135 { "SINCE", ITSINCE
},
136 { "SMALLER", ITSMALLER
},
137 { "SUBJECT", ITSUBJECT
},
141 { "UNANSWERED", ITUNANSWERED
},
142 { "UNDELETED", ITUNDELETED
},
143 { "UNDRAFT", ITUNDRAFT
},
144 { "UNFLAGGED", ITUNFLAGGED
},
145 { "UNKEYWORD", ITUNKEYWORD
},
146 { "UNSEEN", ITUNSEEN
},
150 static struct itnode
{
159 static const char *begin
;
161 static enum okay
itparse(const char *spec
, char **xp
, int sub
);
162 static enum okay
itscan(const char *spec
, char **xp
);
163 static enum okay
itsplit(const char *spec
, char **xp
);
164 static enum okay
itstring(void **tp
, const char *spec
, char **xp
);
165 static int itexecute(struct mailbox
*mp
, struct message
*m
,
166 int c
, struct itnode
*n
);
167 static int matchfield(struct message
*m
, const char *field
, const char *what
);
168 static int matchenvelope(struct message
*m
, const char *field
,
170 static char *mkenvelope(struct name
*np
);
171 static int matchmsg(struct message
*m
, const char *what
, int withheader
);
172 static const char *around(const char *cp
);
175 imap_search(const char *spec
, int f
)
177 static char *lastspec
;
181 if (strcmp(spec
, "()")) {
183 lastspec
= sstrdup(spec
);
184 } else if (lastspec
== NULL
) {
185 fprintf(stderr
, "No last SEARCH criteria available.\n");
190 if (imap_search1(spec
, f
) == OKAY
)
193 if (itparse(spec
, &xp
, 0) == STOP
)
197 if (mb
.mb_type
== MB_IMAP
&& needheaders
)
198 imap_getheaders(1, msgCount
);
199 for (i
= 0; i
< msgCount
; i
++) {
200 if (message
[i
].m_flag
&MHIDDEN
)
202 if (f
== MDELETED
|| (message
[i
].m_flag
&MDELETED
) == 0)
203 if (itexecute(&mb
, &message
[i
], i
+1, ittree
))
210 itparse(const char *spec
, char **xp
, int sub
)
213 struct itnode n
, *z
, *_ittree
;
217 while ((ok
= itscan(spec
, xp
)) == OKAY
&& itoken
!= ITBAD
&&
220 memset(&n
, 0, sizeof n
);
235 fprintf(stderr
, "Excess in \")\".\n");
242 if (itparse(spec
, xp
, sub
+1) == STOP
)
245 if ((n
.n_x
= ittree
) == NULL
) {
247 "Criterion for NOT missing: >>> %s <<<\n",
254 /* <search-key1> <search-key2> */
256 if (itparse(spec
, xp
, sub
+1) == STOP
)
258 if ((n
.n_x
= ittree
) == NULL
) {
259 fprintf(stderr
, "First criterion for OR "
260 "missing: >>> %s <<<\n",
265 if (itparse(spec
, xp
, sub
+1) == STOP
)
268 if ((n
.n_y
= ittree
) == NULL
) {
269 fprintf(stderr
, "Second criterion for OR "
270 "missing: >>> %s <<<\n",
282 if (ittree
== NULL
) {
283 ittree
= salloc(sizeof *ittree
);
287 ittree
= salloc(sizeof *ittree
);
288 ittree
->n_token
= ITAND
;
290 ittree
->n_y
= salloc(sizeof*ittree
->n_y
);
293 if (sub
&& level
== 0)
300 itscan(const char *spec
, char **xp
)
304 while (spacechar(*spec
&0377))
307 *xp
= (char *)&spec
[1];
312 *xp
= (char *)&spec
[1];
316 while (spacechar(*spec
&0377))
322 for (i
= 0; strings
[i
].s_string
; i
++) {
323 n
= strlen(strings
[i
].s_string
);
324 if (ascncasecmp(spec
, strings
[i
].s_string
, n
) == 0 &&
325 (spacechar(spec
[n
]&0377) || spec
[n
] == '\0'
326 || spec
[n
] == '(' || spec
[n
] == ')')) {
327 itoken
= strings
[i
].s_token
;
329 while (spacechar(*spec
&0377))
331 return itsplit(spec
, xp
);
334 if (digitchar(*spec
&0377)) {
335 inumber
= strtoul(spec
, xp
, 10);
336 if (spacechar(**xp
&0377) || **xp
== '\0' ||
337 **xp
== '(' || **xp
== ')') {
342 fprintf(stderr
, "Bad SEARCH criterion \"");
343 while (*spec
&& !spacechar(*spec
&0377) &&
344 *spec
!= '(' && *spec
!= ')') {
345 putc(*spec
&0377, stderr
);
348 fprintf(stderr
, "\": >>> %s <<<\n", around(*xp
));
354 itsplit(const char *spec
, char **xp
)
369 return itstring(&iargs
[0], spec
, xp
);
379 if (itstring(&iargs
[0], spec
, xp
) != OKAY
)
381 if ((t
= imap_read_date(iargs
[0])) == (time_t)-1) {
382 fprintf(stderr
, "Invalid date \"%s\": >>> %s <<<\n",
383 (char *)iargs
[0], around(*xp
));
389 /* <field-name> <string> */
391 if (itstring(&iargs
[0], spec
, xp
) != OKAY
)
394 return itstring(&iargs
[1], spec
, xp
);
398 if (itstring(&iargs
[0], spec
, xp
) != OKAY
)
400 if (asccasecmp(iargs
[0], "\\Seen") == 0)
402 else if (asccasecmp(iargs
[0], "\\Deleted") == 0)
404 else if (asccasecmp(iargs
[0], "\\Recent") == 0)
406 else if (asccasecmp(iargs
[0], "\\Flagged") == 0)
408 else if (asccasecmp(iargs
[0], "\\Answered") == 0)
410 else if (asccasecmp(iargs
[0], "\\Draft") == 0)
418 if (itstring(&iargs
[0], spec
, xp
) != OKAY
)
420 inumber
= strtoul(iargs
[0], &cp
, 10);
421 if (spacechar(*cp
&0377) || *cp
== '\0')
423 fprintf(stderr
, "Invalid size: >>> %s <<<\n",
429 "Searching for UIDs is not supported: >>> %s <<<\n",
439 itstring(void **tp
, const char *spec
, char **xp
)
444 while (spacechar(*spec
&0377))
446 if (*spec
== '\0' || *spec
== '(' || *spec
== ')') {
447 fprintf(stderr
, "Missing string argument: >>> %s <<<\n",
448 around(&(*xp
)[spec
- *xp
]));
451 ap
= *tp
= salloc(strlen(spec
) + 1);
454 if (inquote
&& **xp
== '\\')
456 else if (**xp
== '"')
458 else if (!inquote
&& (spacechar(**xp
&0377) ||
459 **xp
== '(' || **xp
== ')')) {
469 itexecute(struct mailbox
*mp
, struct message
*m
, int c
, struct itnode
*n
)
471 char *cp
, *line
= NULL
;
476 fprintf(stderr
, "Internal error: Empty node in SEARCH tree.\n");
479 switch (n
->n_token
) {
483 if (m
->m_time
== 0 && (m
->m_flag
&MNOFROM
) == 0 &&
484 (ibuf
= setinput(mp
, m
, NEED_HEADER
)) != NULL
) {
485 if (readline(ibuf
, &line
, &linesize
) > 0)
486 m
->m_time
= unixtime(line
);
494 if ((cp
= hfield("date", m
)) != NULL
)
495 m
->m_date
= rfctime(cp
);
500 switch (n
->n_token
) {
502 fprintf(stderr
, "Internal SEARCH error: Lost token %d\n",
506 return itexecute(mp
, m
, c
, n
->n_x
) &
507 itexecute(mp
, m
, c
, n
->n_y
);
509 return (unsigned long)c
== n
->n_n
;
513 return (m
->m_flag
&MANSWERED
) != 0;
515 return matchenvelope(m
, "bcc", n
->n_v
);
517 return (unsigned long)m
->m_time
< n
->n_n
;
519 return matchmsg(m
, n
->n_v
, 0);
521 return matchenvelope(m
, "cc", n
->n_v
);
523 return (m
->m_flag
&MDELETED
) != 0;
525 return (m
->m_flag
&MDRAFTED
) != 0;
527 return (m
->m_flag
&MFLAGGED
) != 0;
529 return matchenvelope(m
, "from", n
->n_v
);
531 return matchfield(m
, n
->n_v
, n
->n_w
);
533 return (m
->m_flag
& n
->n_n
) != 0;
535 return m
->m_xsize
> n
->n_n
;
537 return (m
->m_flag
&(MNEW
|MREAD
)) == MNEW
;
539 return !itexecute(mp
, m
, c
, n
->n_x
);
541 return (m
->m_flag
&MNEW
) == 0;
543 return ((unsigned long)m
->m_time
>= n
->n_n
&&
544 (unsigned long)m
->m_time
< n
->n_n
+ 86400);
546 return itexecute(mp
, m
, c
, n
->n_x
) |
547 itexecute(mp
, m
, c
, n
->n_y
);
549 return (m
->m_flag
&MNEW
) != 0;
551 return (m
->m_flag
&MREAD
) != 0;
553 return (unsigned long)m
->m_date
< n
->n_n
;
555 return ((unsigned long)m
->m_date
>= n
->n_n
&&
556 (unsigned long)m
->m_date
< n
->n_n
+ 86400);
558 return (unsigned long)m
->m_date
>= n
->n_n
;
560 return (unsigned long)m
->m_time
>= n
->n_n
;
562 return (unsigned long)m
->m_xsize
< n
->n_n
;
564 return matchfield(m
, "subject", n
->n_v
);
566 return matchmsg(m
, n
->n_v
, 1);
568 return matchenvelope(m
, "to", n
->n_v
);
570 return (m
->m_flag
&MANSWERED
) == 0;
572 return (m
->m_flag
&MDELETED
) == 0;
574 return (m
->m_flag
&MDRAFTED
) == 0;
576 return (m
->m_flag
&MFLAGGED
) == 0;
578 return (m
->m_flag
& n
->n_n
) == 0;
580 return (m
->m_flag
&MREAD
) == 0;
585 matchfield(struct message
*m
, const char *field
, const char *what
)
590 if ((in
.s
= hfield(imap_unquotestr(field
), m
)) == NULL
)
593 mime_fromhdr(&in
, &out
, TD_ICONV
);
594 what
= imap_unquotestr(what
);
595 i
= substr(out
.s
, what
);
601 matchenvelope(struct message
*m
, const char *field
, const char *what
)
606 if ((cp
= hfield(imap_unquotestr(field
), m
)) == NULL
)
608 what
= imap_unquotestr(what
);
609 np
= sextract(cp
, GFULL
);
611 if (substr(np
->n_name
, what
))
613 if (substr(mkenvelope(np
), what
))
621 mkenvelope(struct name
*np
)
625 char *realname
= NULL
, *sourceaddr
= NULL
,
626 *localpart
= NULL
, *domainpart
= NULL
,
629 int level
= 0, hadphrase
= 0;
631 in
.s
= np
->n_fullname
;
633 mime_fromhdr(&in
, &out
, TD_ICONV
);
634 rp
= ip
= ac_alloc(strlen(out
.s
) + 1);
635 for (cp
= out
.s
; *cp
; cp
++) {
641 if (*cp
== '\\' && cp
[1])
647 while (cp
> out
.s
&& blankchar(cp
[-1]&0377))
651 if (xp
< &cp
[-1] && *xp
== '"' && cp
[-1] == '"') {
662 if (hadphrase
++ == 0)
682 localpart
= savestr(np
->n_name
);
683 if ((cp
= strrchr(localpart
, '@')) != NULL
) {
687 ep
= salloc(epsize
= strlen(np
->n_fullname
) * 2 + 40);
688 snprintf(ep
, epsize
, "(%s %s %s %s)",
689 realname
? imap_quotestr(realname
) : "NIL",
690 sourceaddr
? imap_quotestr(sourceaddr
) : "NIL",
691 localpart
? imap_quotestr(localpart
) : "NIL",
692 domainpart
? imap_quotestr(domainpart
) : "NIL");
698 matchmsg(struct message
*m
, const char *what
, int withheader
)
700 char *tempFile
, *line
= NULL
;
701 size_t linesize
, linelen
, count
;
705 if ((fp
= Ftemp(&tempFile
, "Ra", "w+", 0600, 1)) == NULL
)
709 if (send(m
, fp
, NULL
, NULL
, SEND_TOSRCH
, NULL
) < 0)
714 line
= smalloc(linesize
= LINESIZE
);
717 while (fgetline(&line
, &linesize
, &count
, &linelen
, fp
, 0))
720 what
= imap_unquotestr(what
);
721 while (fgetline(&line
, &linesize
, &count
, &linelen
, fp
, 0))
722 if (substr(line
, what
)) {
732 #define SURROUNDING 16
734 around(const char *cp
)
737 static char ab
[2*SURROUNDING
+1];
739 for (i
= 0; i
< SURROUNDING
&& cp
> begin
; i
++)
741 for (i
= 0; i
< (int)sizeof ab
- 1; i
++)