2 * S-nail - 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
47 * Mail -- a mail program
49 * Client-side implementation of the IMAP SEARCH command. This is used
50 * for folders not located on IMAP servers, or for IMAP servers that do
51 * not implement the SEARCH command.
98 static unsigned long inumber
;
99 static void *iargs
[2];
100 static int needheaders
;
102 static struct itlex
{
103 const char *s_string
;
107 { "ANSWERED", ITANSWERED
},
109 { "BEFORE", ITBEFORE
},
112 { "DELETED", ITDELETED
},
113 { "DRAFT", ITDRAFT
},
114 { "FLAGGED", ITFLAGGED
},
116 { "HEADER", ITHEADER
},
117 { "KEYWORD", ITKEYWORD
},
118 { "LARGER", ITLARGER
},
124 { "RECENT", ITRECENT
},
126 { "SENTBEFORE", ITSENTBEFORE
},
127 { "SENTON", ITSENTON
},
128 { "SENTSINCE", ITSENTSINCE
},
129 { "SINCE", ITSINCE
},
130 { "SMALLER", ITSMALLER
},
131 { "SUBJECT", ITSUBJECT
},
135 { "UNANSWERED", ITUNANSWERED
},
136 { "UNDELETED", ITUNDELETED
},
137 { "UNDRAFT", ITUNDRAFT
},
138 { "UNFLAGGED", ITUNFLAGGED
},
139 { "UNKEYWORD", ITUNKEYWORD
},
140 { "UNSEEN", ITUNSEEN
},
144 static struct itnode
{
153 static const char *begin
;
155 static enum okay
itparse(const char *spec
, char **xp
, int sub
);
156 static enum okay
itscan(const char *spec
, char **xp
);
157 static enum okay
itsplit(const char *spec
, char **xp
);
158 static enum okay
itstring(void **tp
, const char *spec
, char **xp
);
159 static int itexecute(struct mailbox
*mp
, struct message
*m
,
160 int c
, struct itnode
*n
);
161 static int matchfield(struct message
*m
, const char *field
, const char *what
);
162 static int matchenvelope(struct message
*m
, const char *field
,
164 static char *mkenvelope(struct name
*np
);
165 static int matchmsg(struct message
*m
, const char *what
, int withheader
);
166 static const char *around(const char *cp
);
169 imap_search(const char *spec
, int f
)
171 static char *lastspec
;
175 if (strcmp(spec
, "()")) {
177 lastspec
= sstrdup(spec
);
178 } else if (lastspec
== NULL
) {
179 fprintf(stderr
, "No last SEARCH criteria available.\n");
184 if (imap_search1(spec
, f
) == OKAY
)
187 if (itparse(spec
, &xp
, 0) == STOP
)
191 if (mb
.mb_type
== MB_IMAP
&& needheaders
)
192 imap_getheaders(1, msgCount
);
193 for (i
= 0; i
< msgCount
; i
++) {
194 if (message
[i
].m_flag
&MHIDDEN
)
196 if (f
== MDELETED
|| (message
[i
].m_flag
&MDELETED
) == 0)
197 if (itexecute(&mb
, &message
[i
], i
+1, ittree
))
204 itparse(const char *spec
, char **xp
, int sub
)
207 struct itnode n
, *z
, *_ittree
;
211 while ((ok
= itscan(spec
, xp
)) == OKAY
&& itoken
!= ITBAD
&&
214 memset(&n
, 0, sizeof n
);
229 fprintf(stderr
, "Excess in \")\".\n");
236 if (itparse(spec
, xp
, sub
+1) == STOP
)
239 if ((n
.n_x
= ittree
) == NULL
) {
241 "Criterion for NOT missing: >>> %s <<<\n",
248 /* <search-key1> <search-key2> */
250 if (itparse(spec
, xp
, sub
+1) == STOP
)
252 if ((n
.n_x
= ittree
) == NULL
) {
253 fprintf(stderr
, "First criterion for OR "
254 "missing: >>> %s <<<\n",
259 if (itparse(spec
, xp
, sub
+1) == STOP
)
262 if ((n
.n_y
= ittree
) == NULL
) {
263 fprintf(stderr
, "Second criterion for OR "
264 "missing: >>> %s <<<\n",
276 if (ittree
== NULL
) {
277 ittree
= salloc(sizeof *ittree
);
281 ittree
= salloc(sizeof *ittree
);
282 ittree
->n_token
= ITAND
;
284 ittree
->n_y
= salloc(sizeof*ittree
->n_y
);
287 if (sub
&& level
== 0)
294 itscan(const char *spec
, char **xp
)
298 while (spacechar(*spec
&0377))
301 *xp
= (char *)&spec
[1];
306 *xp
= (char *)&spec
[1];
310 while (spacechar(*spec
&0377))
316 for (i
= 0; strings
[i
].s_string
; i
++) {
317 n
= strlen(strings
[i
].s_string
);
318 if (ascncasecmp(spec
, strings
[i
].s_string
, n
) == 0 &&
319 (spacechar(spec
[n
]&0377) || spec
[n
] == '\0'
320 || spec
[n
] == '(' || spec
[n
] == ')')) {
321 itoken
= strings
[i
].s_token
;
323 while (spacechar(*spec
&0377))
325 return itsplit(spec
, xp
);
328 if (digitchar(*spec
&0377)) {
329 inumber
= strtoul(spec
, xp
, 10);
330 if (spacechar(**xp
&0377) || **xp
== '\0' ||
331 **xp
== '(' || **xp
== ')') {
336 fprintf(stderr
, "Bad SEARCH criterion \"");
337 while (*spec
&& !spacechar(*spec
&0377) &&
338 *spec
!= '(' && *spec
!= ')') {
339 putc(*spec
&0377, stderr
);
342 fprintf(stderr
, "\": >>> %s <<<\n", around(*xp
));
348 itsplit(const char *spec
, char **xp
)
363 return itstring(&iargs
[0], spec
, xp
);
373 if (itstring(&iargs
[0], spec
, xp
) != OKAY
)
375 if ((t
= imap_read_date(iargs
[0])) == (time_t)-1) {
376 fprintf(stderr
, "Invalid date \"%s\": >>> %s <<<\n",
377 (char *)iargs
[0], around(*xp
));
383 /* <field-name> <string> */
385 if (itstring(&iargs
[0], spec
, xp
) != OKAY
)
388 return itstring(&iargs
[1], spec
, xp
);
392 if (itstring(&iargs
[0], spec
, xp
) != OKAY
)
394 if (asccasecmp(iargs
[0], "\\Seen") == 0)
396 else if (asccasecmp(iargs
[0], "\\Deleted") == 0)
398 else if (asccasecmp(iargs
[0], "\\Recent") == 0)
400 else if (asccasecmp(iargs
[0], "\\Flagged") == 0)
402 else if (asccasecmp(iargs
[0], "\\Answered") == 0)
404 else if (asccasecmp(iargs
[0], "\\Draft") == 0)
412 if (itstring(&iargs
[0], spec
, xp
) != OKAY
)
414 inumber
= strtoul(iargs
[0], &cp
, 10);
415 if (spacechar(*cp
&0377) || *cp
== '\0')
417 fprintf(stderr
, "Invalid size: >>> %s <<<\n",
423 "Searching for UIDs is not supported: >>> %s <<<\n",
433 itstring(void **tp
, const char *spec
, char **xp
)
438 while (spacechar(*spec
&0377))
440 if (*spec
== '\0' || *spec
== '(' || *spec
== ')') {
441 fprintf(stderr
, "Missing string argument: >>> %s <<<\n",
442 around(&(*xp
)[spec
- *xp
]));
445 ap
= *tp
= salloc(strlen(spec
) + 1);
448 if (inquote
&& **xp
== '\\')
450 else if (**xp
== '"')
452 else if (!inquote
&& (spacechar(**xp
&0377) ||
453 **xp
== '(' || **xp
== ')')) {
463 itexecute(struct mailbox
*mp
, struct message
*m
, int c
, struct itnode
*n
)
465 char *cp
, *line
= NULL
;
470 fprintf(stderr
, "Internal error: Empty node in SEARCH tree.\n");
473 switch (n
->n_token
) {
477 if (m
->m_time
== 0 && (m
->m_flag
&MNOFROM
) == 0 &&
478 (ibuf
= setinput(mp
, m
, NEED_HEADER
)) != NULL
) {
479 if (readline(ibuf
, &line
, &linesize
) > 0)
480 m
->m_time
= unixtime(line
);
488 if ((cp
= hfield1("date", m
)) != NULL
)
489 m
->m_date
= rfctime(cp
);
494 switch (n
->n_token
) {
496 fprintf(stderr
, "Internal SEARCH error: Lost token %d\n",
500 return itexecute(mp
, m
, c
, n
->n_x
) &
501 itexecute(mp
, m
, c
, n
->n_y
);
503 return (unsigned long)c
== n
->n_n
;
507 return (m
->m_flag
&MANSWERED
) != 0;
509 return matchenvelope(m
, "bcc", n
->n_v
);
511 return (unsigned long)m
->m_time
< n
->n_n
;
513 return matchmsg(m
, n
->n_v
, 0);
515 return matchenvelope(m
, "cc", n
->n_v
);
517 return (m
->m_flag
&MDELETED
) != 0;
519 return (m
->m_flag
&MDRAFTED
) != 0;
521 return (m
->m_flag
&MFLAGGED
) != 0;
523 return matchenvelope(m
, "from", n
->n_v
);
525 return matchfield(m
, n
->n_v
, n
->n_w
);
527 return (m
->m_flag
& n
->n_n
) != 0;
529 return m
->m_xsize
> n
->n_n
;
531 return (m
->m_flag
&(MNEW
|MREAD
)) == MNEW
;
533 return !itexecute(mp
, m
, c
, n
->n_x
);
535 return (m
->m_flag
&MNEW
) == 0;
537 return ((unsigned long)m
->m_time
>= n
->n_n
&&
538 (unsigned long)m
->m_time
< n
->n_n
+ 86400);
540 return itexecute(mp
, m
, c
, n
->n_x
) |
541 itexecute(mp
, m
, c
, n
->n_y
);
543 return (m
->m_flag
&MNEW
) != 0;
545 return (m
->m_flag
&MREAD
) != 0;
547 return (unsigned long)m
->m_date
< n
->n_n
;
549 return ((unsigned long)m
->m_date
>= n
->n_n
&&
550 (unsigned long)m
->m_date
< n
->n_n
+ 86400);
552 return (unsigned long)m
->m_date
>= n
->n_n
;
554 return (unsigned long)m
->m_time
>= n
->n_n
;
556 return (unsigned long)m
->m_xsize
< n
->n_n
;
558 return matchfield(m
, "subject", n
->n_v
);
560 return matchmsg(m
, n
->n_v
, 1);
562 return matchenvelope(m
, "to", n
->n_v
);
564 return (m
->m_flag
&MANSWERED
) == 0;
566 return (m
->m_flag
&MDELETED
) == 0;
568 return (m
->m_flag
&MDRAFTED
) == 0;
570 return (m
->m_flag
&MFLAGGED
) == 0;
572 return (m
->m_flag
& n
->n_n
) == 0;
574 return (m
->m_flag
&MREAD
) == 0;
579 matchfield(struct message
*m
, const char *field
, const char *what
)
584 if ((in
.s
= hfieldX(imap_unquotestr(field
), m
)) == NULL
)
587 mime_fromhdr(&in
, &out
, TD_ICONV
);
588 what
= imap_unquotestr(what
);
589 i
= substr(out
.s
, what
);
595 matchenvelope(struct message
*m
, const char *field
, const char *what
)
600 if ((cp
= hfieldX(imap_unquotestr(field
), m
)) == NULL
)
602 what
= imap_unquotestr(what
);
603 np
= lextract(cp
, GFULL
);
605 if (substr(np
->n_name
, what
))
607 if (substr(mkenvelope(np
), what
))
615 mkenvelope(struct name
*np
)
619 char *realname
= NULL
, *sourceaddr
= NULL
,
620 *localpart
= NULL
, *domainpart
= NULL
,
623 int level
= 0, hadphrase
= 0;
625 in
.s
= np
->n_fullname
;
627 mime_fromhdr(&in
, &out
, TD_ICONV
);
628 rp
= ip
= ac_alloc(strlen(out
.s
) + 1);
629 for (cp
= out
.s
; *cp
; cp
++) {
635 if (*cp
== '\\' && cp
[1])
641 while (cp
> out
.s
&& blankchar(cp
[-1]&0377))
645 if (xp
< &cp
[-1] && *xp
== '"' && cp
[-1] == '"') {
656 if (hadphrase
++ == 0)
676 localpart
= savestr(np
->n_name
);
677 if ((cp
= strrchr(localpart
, '@')) != NULL
) {
681 ep
= salloc(epsize
= strlen(np
->n_fullname
) * 2 + 40);
682 snprintf(ep
, epsize
, "(%s %s %s %s)",
683 realname
? imap_quotestr(realname
) : "NIL",
684 sourceaddr
? imap_quotestr(sourceaddr
) : "NIL",
685 localpart
? imap_quotestr(localpart
) : "NIL",
686 domainpart
? imap_quotestr(domainpart
) : "NIL");
692 matchmsg(struct message
*m
, const char *what
, int withheader
)
694 char *tempFile
, *line
= NULL
;
695 size_t linesize
, linelen
, count
;
699 if ((fp
= Ftemp(&tempFile
, "Ra", "w+", 0600, 1)) == NULL
)
703 if (send(m
, fp
, NULL
, NULL
, SEND_TOSRCH
, NULL
) < 0)
708 line
= smalloc(linesize
= LINESIZE
);
711 while (fgetline(&line
, &linesize
, &count
, &linelen
, fp
, 0))
714 what
= imap_unquotestr(what
);
715 while (fgetline(&line
, &linesize
, &count
, &linelen
, fp
, 0))
716 if (substr(line
, what
)) {
726 #define SURROUNDING 16
728 around(const char *cp
)
731 static char ab
[2*SURROUNDING
+1];
733 for (i
= 0; i
< SURROUNDING
&& cp
> begin
; i
++)
735 for (i
= 0; i
< (int)sizeof ab
- 1; i
++)