2 * Heirloom mailx - a mail user agent derived from Berkeley Mail.
4 * Copyright (c) 2000-2004 Gunnar Ritter, Freiburg i. Br., Germany.
8 * Gunnar Ritter. All rights reserved.
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 * 3. All advertising materials mentioning features or use of this software
19 * must display the following acknowledgement:
20 * This product includes software developed by Gunnar Ritter
21 * and his contributors.
22 * 4. Neither the name of Gunnar Ritter nor the names of his contributors
23 * may be used to endorse or promote products derived from this software
24 * without specific prior written permission.
26 * THIS SOFTWARE IS PROVIDED BY GUNNAR RITTER AND CONTRIBUTORS ``AS IS'' AND
27 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
28 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29 * ARE DISCLAIMED. IN NO EVENT SHALL GUNNAR RITTER OR CONTRIBUTORS BE LIABLE
30 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
31 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
32 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
33 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
35 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
41 static char sccsid
[] = "@(#)imap_search.c 1.29 (gritter) 3/4/06";
52 * Mail -- a mail program
54 * Client-side implementation of the IMAP SEARCH command. This is used
55 * for folders not located on IMAP servers, or for IMAP servers that do
56 * not implement the SEARCH command.
103 static unsigned long inumber
;
104 static void *iargs
[2];
105 static int needheaders
;
107 static struct itlex
{
108 const char *s_string
;
112 { "ANSWERED", ITANSWERED
},
114 { "BEFORE", ITBEFORE
},
117 { "DELETED", ITDELETED
},
118 { "DRAFT", ITDRAFT
},
119 { "FLAGGED", ITFLAGGED
},
121 { "HEADER", ITHEADER
},
122 { "KEYWORD", ITKEYWORD
},
123 { "LARGER", ITLARGER
},
129 { "RECENT", ITRECENT
},
131 { "SENTBEFORE", ITSENTBEFORE
},
132 { "SENTON", ITSENTON
},
133 { "SENTSINCE", ITSENTSINCE
},
134 { "SINCE", ITSINCE
},
135 { "SMALLER", ITSMALLER
},
136 { "SUBJECT", ITSUBJECT
},
140 { "UNANSWERED", ITUNANSWERED
},
141 { "UNDELETED", ITUNDELETED
},
142 { "UNDRAFT", ITUNDRAFT
},
143 { "UNFLAGGED", ITUNFLAGGED
},
144 { "UNKEYWORD", ITUNKEYWORD
},
145 { "UNSEEN", ITUNSEEN
},
149 static struct itnode
{
158 static const char *begin
;
160 static enum okay
itparse(const char *spec
, char **xp
, int sub
);
161 static enum okay
itscan(const char *spec
, char **xp
);
162 static enum okay
itsplit(const char *spec
, char **xp
);
163 static enum okay
itstring(void **tp
, const char *spec
, char **xp
);
164 static int itexecute(struct mailbox
*mp
, struct message
*m
,
165 int c
, struct itnode
*n
);
166 static int matchfield(struct message
*m
, const char *field
, const char *what
);
167 static int matchenvelope(struct message
*m
, const char *field
,
169 static char *mkenvelope(struct name
*np
);
170 static int matchmsg(struct message
*m
, const char *what
, int withheader
);
171 static const char *around(const char *cp
);
174 imap_search(const char *spec
, int f
)
176 static char *lastspec
;
180 if (strcmp(spec
, "()")) {
182 lastspec
= sstrdup(spec
);
183 } else if (lastspec
== NULL
) {
184 fprintf(stderr
, "No last SEARCH criteria available.\n");
189 if (imap_search1(spec
, f
) == OKAY
)
192 if (itparse(spec
, &xp
, 0) == STOP
)
196 if (mb
.mb_type
== MB_IMAP
&& needheaders
)
197 imap_getheaders(1, msgCount
);
198 for (i
= 0; i
< msgCount
; i
++) {
199 if (message
[i
].m_flag
&MHIDDEN
)
201 if (f
== MDELETED
|| (message
[i
].m_flag
&MDELETED
) == 0)
202 if (itexecute(&mb
, &message
[i
], i
+1, ittree
))
209 itparse(const char *spec
, char **xp
, int sub
)
212 struct itnode n
, *z
, *_ittree
;
216 while ((ok
= itscan(spec
, xp
)) == OKAY
&& itoken
!= ITBAD
&&
219 memset(&n
, 0, sizeof n
);
234 fprintf(stderr
, "Excess in \")\".\n");
241 if (itparse(spec
, xp
, sub
+1) == STOP
)
244 if ((n
.n_x
= ittree
) == NULL
) {
246 "Criterion for NOT missing: >>> %s <<<\n",
253 /* <search-key1> <search-key2> */
255 if (itparse(spec
, xp
, sub
+1) == STOP
)
257 if ((n
.n_x
= ittree
) == NULL
) {
258 fprintf(stderr
, "First criterion for OR "
259 "missing: >>> %s <<<\n",
264 if (itparse(spec
, xp
, sub
+1) == STOP
)
267 if ((n
.n_y
= ittree
) == NULL
) {
268 fprintf(stderr
, "Second criterion for OR "
269 "missing: >>> %s <<<\n",
281 if (ittree
== NULL
) {
282 ittree
= salloc(sizeof *ittree
);
286 ittree
= salloc(sizeof *ittree
);
287 ittree
->n_token
= ITAND
;
289 ittree
->n_y
= salloc(sizeof*ittree
->n_y
);
292 if (sub
&& level
== 0)
299 itscan(const char *spec
, char **xp
)
303 while (spacechar(*spec
&0377))
306 *xp
= (char *)&spec
[1];
311 *xp
= (char *)&spec
[1];
315 while (spacechar(*spec
&0377))
321 for (i
= 0; strings
[i
].s_string
; i
++) {
322 n
= strlen(strings
[i
].s_string
);
323 if (ascncasecmp(spec
, strings
[i
].s_string
, n
) == 0 &&
324 (spacechar(spec
[n
]&0377) || spec
[n
] == '\0'
325 || spec
[n
] == '(' || spec
[n
] == ')')) {
326 itoken
= strings
[i
].s_token
;
328 while (spacechar(*spec
&0377))
330 return itsplit(spec
, xp
);
333 if (digitchar(*spec
&0377)) {
334 inumber
= strtoul(spec
, xp
, 10);
335 if (spacechar(**xp
&0377) || **xp
== '\0' ||
336 **xp
== '(' || **xp
== ')') {
341 fprintf(stderr
, "Bad SEARCH criterion \"");
342 while (*spec
&& !spacechar(*spec
&0377) &&
343 *spec
!= '(' && *spec
!= ')') {
344 putc(*spec
&0377, stderr
);
347 fprintf(stderr
, "\": >>> %s <<<\n", around(*xp
));
353 itsplit(const char *spec
, char **xp
)
368 return itstring(&iargs
[0], spec
, xp
);
378 if (itstring(&iargs
[0], spec
, xp
) != OKAY
)
380 if ((t
= imap_read_date(iargs
[0])) == (time_t)-1) {
381 fprintf(stderr
, "Invalid date \"%s\": >>> %s <<<\n",
382 (char *)iargs
[0], around(*xp
));
388 /* <field-name> <string> */
390 if (itstring(&iargs
[0], spec
, xp
) != OKAY
)
393 return itstring(&iargs
[1], spec
, xp
);
397 if (itstring(&iargs
[0], spec
, xp
) != OKAY
)
399 if (asccasecmp(iargs
[0], "\\Seen") == 0)
401 else if (asccasecmp(iargs
[0], "\\Deleted") == 0)
403 else if (asccasecmp(iargs
[0], "\\Recent") == 0)
405 else if (asccasecmp(iargs
[0], "\\Flagged") == 0)
407 else if (asccasecmp(iargs
[0], "\\Answered") == 0)
409 else if (asccasecmp(iargs
[0], "\\Draft") == 0)
417 if (itstring(&iargs
[0], spec
, xp
) != OKAY
)
419 inumber
= strtoul(iargs
[0], &cp
, 10);
420 if (spacechar(*cp
&0377) || *cp
== '\0')
422 fprintf(stderr
, "Invalid size: >>> %s <<<\n",
428 "Searching for UIDs is not supported: >>> %s <<<\n",
438 itstring(void **tp
, const char *spec
, char **xp
)
443 while (spacechar(*spec
&0377))
445 if (*spec
== '\0' || *spec
== '(' || *spec
== ')') {
446 fprintf(stderr
, "Missing string argument: >>> %s <<<\n",
447 around(&(*xp
)[spec
- *xp
]));
450 ap
= *tp
= salloc(strlen(spec
) + 1);
453 if (inquote
&& **xp
== '\\')
455 else if (**xp
== '"')
457 else if (!inquote
&& (spacechar(**xp
&0377) ||
458 **xp
== '(' || **xp
== ')')) {
468 itexecute(struct mailbox
*mp
, struct message
*m
, int c
, struct itnode
*n
)
470 char *cp
, *line
= NULL
;
475 fprintf(stderr
, "Internal error: Empty node in SEARCH tree.\n");
478 switch (n
->n_token
) {
482 if (m
->m_time
== 0 && (m
->m_flag
&MNOFROM
) == 0 &&
483 (ibuf
= setinput(mp
, m
, NEED_HEADER
)) != NULL
) {
484 if (readline(ibuf
, &line
, &linesize
) > 0)
485 m
->m_time
= unixtime(line
);
493 if ((cp
= hfield("date", m
)) != NULL
)
494 m
->m_date
= rfctime(cp
);
499 switch (n
->n_token
) {
501 fprintf(stderr
, "Internal SEARCH error: Lost token %d\n",
505 return itexecute(mp
, m
, c
, n
->n_x
) &
506 itexecute(mp
, m
, c
, n
->n_y
);
512 return (m
->m_flag
&MANSWERED
) != 0;
514 return matchenvelope(m
, "bcc", n
->n_v
);
516 return m
->m_time
< n
->n_n
;
518 return matchmsg(m
, n
->n_v
, 0);
520 return matchenvelope(m
, "cc", n
->n_v
);
522 return (m
->m_flag
&MDELETED
) != 0;
524 return (m
->m_flag
&MDRAFTED
) != 0;
526 return (m
->m_flag
&MFLAGGED
) != 0;
528 return matchenvelope(m
, "from", n
->n_v
);
530 return matchfield(m
, n
->n_v
, n
->n_w
);
532 return (m
->m_flag
& n
->n_n
) != 0;
534 return m
->m_xsize
> n
->n_n
;
536 return (m
->m_flag
&(MNEW
|MREAD
)) == MNEW
;
538 return !itexecute(mp
, m
, c
, n
->n_x
);
540 return (m
->m_flag
&MNEW
) == 0;
542 return m
->m_time
>= n
->n_n
&& m
->m_time
< n
->n_n
+ 86400;
544 return itexecute(mp
, m
, c
, n
->n_x
) |
545 itexecute(mp
, m
, c
, n
->n_y
);
547 return (m
->m_flag
&MNEW
) != 0;
549 return (m
->m_flag
&MREAD
) != 0;
551 return m
->m_date
< n
->n_n
;
553 return m
->m_date
>= n
->n_n
&& m
->m_date
< n
->n_n
+ 86400;
555 return m
->m_date
>= n
->n_n
;
557 return m
->m_time
>= n
->n_n
;
559 return m
->m_xsize
< n
->n_n
;
561 return matchfield(m
, "subject", n
->n_v
);
563 return matchmsg(m
, n
->n_v
, 1);
565 return matchenvelope(m
, "to", n
->n_v
);
567 return (m
->m_flag
&MANSWERED
) == 0;
569 return (m
->m_flag
&MDELETED
) == 0;
571 return (m
->m_flag
&MDRAFTED
) == 0;
573 return (m
->m_flag
&MFLAGGED
) == 0;
575 return (m
->m_flag
& n
->n_n
) == 0;
577 return (m
->m_flag
&MREAD
) == 0;
582 matchfield(struct message
*m
, const char *field
, const char *what
)
587 if ((in
.s
= hfield(imap_unquotestr(field
), m
)) == NULL
)
590 mime_fromhdr(&in
, &out
, TD_ICONV
);
591 what
= imap_unquotestr(what
);
592 i
= substr(out
.s
, what
);
598 matchenvelope(struct message
*m
, const char *field
, const char *what
)
603 if ((cp
= hfield(imap_unquotestr(field
), m
)) == NULL
)
605 what
= imap_unquotestr(what
);
606 np
= sextract(cp
, GFULL
);
608 if (substr(np
->n_name
, what
))
610 if (substr(mkenvelope(np
), what
))
618 mkenvelope(struct name
*np
)
622 char *realname
= NULL
, *sourceaddr
= NULL
,
623 *localpart
= NULL
, *domainpart
= NULL
,
626 int level
= 0, hadphrase
= 0;
628 in
.s
= np
->n_fullname
;
630 mime_fromhdr(&in
, &out
, TD_ICONV
);
631 rp
= ip
= ac_alloc(strlen(out
.s
) + 1);
632 for (cp
= out
.s
; *cp
; cp
++) {
638 if (*cp
== '\\' && cp
[1])
644 while (cp
> out
.s
&& blankchar(cp
[-1]&0377))
648 if (xp
< &cp
[-1] && *xp
== '"' && cp
[-1] == '"') {
659 if (hadphrase
++ == 0)
679 localpart
= savestr(np
->n_name
);
680 if ((cp
= strrchr(localpart
, '@')) != NULL
) {
684 ep
= salloc(epsize
= strlen(np
->n_fullname
) * 2 + 40);
685 snprintf(ep
, epsize
, "(%s %s %s %s)",
686 realname
? imap_quotestr(realname
) : "NIL",
687 sourceaddr
? imap_quotestr(sourceaddr
) : "NIL",
688 localpart
? imap_quotestr(localpart
) : "NIL",
689 domainpart
? imap_quotestr(domainpart
) : "NIL");
695 matchmsg(struct message
*m
, const char *what
, int withheader
)
697 char *tempFile
, *line
= NULL
;
698 size_t linesize
, linelen
, count
;
702 if ((fp
= Ftemp(&tempFile
, "Ra", "w+", 0600, 1)) == NULL
)
706 if (send(m
, fp
, NULL
, NULL
, SEND_TOSRCH
, NULL
) < 0)
711 line
= smalloc(linesize
= LINESIZE
);
714 while (fgetline(&line
, &linesize
, &count
, &linelen
, fp
, 0))
717 what
= imap_unquotestr(what
);
718 while (fgetline(&line
, &linesize
, &count
, &linelen
, fp
, 0))
719 if (substr(line
, what
)) {
729 #define SURROUNDING 16
731 around(const char *cp
)
734 static char ab
[2*SURROUNDING
+1];
736 for (i
= 0; i
< SURROUNDING
&& cp
> begin
; i
++)
738 for (i
= 0; i
< sizeof ab
- 1; i
++)