/src/neomutt/imap/search.c
Line | Count | Source (jump to first uncovered line) |
1 | | /** |
2 | | * @file |
3 | | * IMAP search routines |
4 | | * |
5 | | * @authors |
6 | | * Copyright (C) 1996-1998,2012 Michael R. Elkins <me@mutt.org> |
7 | | * Copyright (C) 1996-1999 Brandon Long <blong@fiction.net> |
8 | | * Copyright (C) 1999-2009,2012,2017 Brendan Cully <brendan@kublai.com> |
9 | | * Copyright (C) 2018 Richard Russon <rich@flatcap.org> |
10 | | * |
11 | | * @copyright |
12 | | * This program is free software: you can redistribute it and/or modify it under |
13 | | * the terms of the GNU General Public License as published by the Free Software |
14 | | * Foundation, either version 2 of the License, or (at your option) any later |
15 | | * version. |
16 | | * |
17 | | * This program is distributed in the hope that it will be useful, but WITHOUT |
18 | | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS |
19 | | * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more |
20 | | * details. |
21 | | * |
22 | | * You should have received a copy of the GNU General Public License along with |
23 | | * this program. If not, see <http://www.gnu.org/licenses/>. |
24 | | */ |
25 | | |
26 | | /** |
27 | | * @page imap_search Search routines |
28 | | * |
29 | | * IMAP search routines |
30 | | */ |
31 | | |
32 | | #include "config.h" |
33 | | #include <stdbool.h> |
34 | | #include <string.h> |
35 | | #include "private.h" |
36 | | #include "mutt/lib.h" |
37 | | #include "email/lib.h" |
38 | | #include "core/lib.h" |
39 | | #include "lib.h" |
40 | | #include "pattern/lib.h" |
41 | | #include "adata.h" |
42 | | #include "mdata.h" |
43 | | |
44 | | // fwd decl, mutually recursive: check_pattern_list, check_pattern |
45 | | static int check_pattern_list(const struct PatternList *patterns); |
46 | | |
47 | | // fwd-decl, mutually recursive: compile_search, compile_search_children |
48 | | static bool compile_search(const struct ImapAccountData *adata, |
49 | | const struct Pattern *pat, struct Buffer *buf); |
50 | | |
51 | | /** |
52 | | * check_pattern - Check whether a pattern can be searched server-side |
53 | | * @param pat Pattern to check |
54 | | * @retval true Pattern can be searched server-side |
55 | | * @retval false Pattern cannot be searched server-side |
56 | | */ |
57 | | static bool check_pattern(const struct Pattern *pat) |
58 | 0 | { |
59 | 0 | switch (pat->op) |
60 | 0 | { |
61 | 0 | case MUTT_PAT_BODY: |
62 | 0 | case MUTT_PAT_HEADER: |
63 | 0 | case MUTT_PAT_WHOLE_MSG: |
64 | 0 | if (pat->string_match) |
65 | 0 | return true; |
66 | 0 | break; |
67 | 0 | case MUTT_PAT_SERVERSEARCH: |
68 | 0 | return true; |
69 | 0 | break; |
70 | 0 | default: |
71 | 0 | if (pat->child && check_pattern_list(pat->child)) |
72 | 0 | return true; |
73 | 0 | break; |
74 | 0 | } |
75 | 0 | return false; |
76 | 0 | } |
77 | | |
78 | | /** |
79 | | * check_pattern_list - Check how many patterns in a list can be searched server-side |
80 | | * @param patterns List of patterns to match |
81 | | * @retval num Number of patterns search that can be searched server-side |
82 | | */ |
83 | | static int check_pattern_list(const struct PatternList *patterns) |
84 | 0 | { |
85 | 0 | int positives = 0; |
86 | |
|
87 | 0 | const struct Pattern *pat = NULL; |
88 | 0 | SLIST_FOREACH(pat, patterns, entries) |
89 | 0 | { |
90 | 0 | positives += check_pattern(pat); |
91 | 0 | } |
92 | |
|
93 | 0 | return positives; |
94 | 0 | } |
95 | | |
96 | | /** |
97 | | * compile_search_children - Compile a search command for a pattern's children |
98 | | * @param adata Imap Account data |
99 | | * @param pat Parent pattern |
100 | | * @param buf Buffer for the resulting command |
101 | | * @retval true Success |
102 | | * @retval false Failure |
103 | | */ |
104 | | static bool compile_search_children(const struct ImapAccountData *adata, |
105 | | const struct Pattern *pat, struct Buffer *buf) |
106 | 0 | { |
107 | 0 | int clauses = check_pattern_list(pat->child); |
108 | 0 | if (clauses == 0) |
109 | 0 | return true; |
110 | | |
111 | 0 | buf_addch(buf, '('); |
112 | |
|
113 | 0 | struct Pattern *c; |
114 | 0 | SLIST_FOREACH(c, pat->child, entries) |
115 | 0 | { |
116 | 0 | if (!check_pattern(c)) |
117 | 0 | continue; |
118 | | |
119 | 0 | if ((pat->op == MUTT_PAT_OR) && (clauses > 1)) |
120 | 0 | buf_addstr(buf, "OR "); |
121 | |
|
122 | 0 | if (!compile_search(adata, c, buf)) |
123 | 0 | return false; |
124 | | |
125 | 0 | if (clauses > 1) |
126 | 0 | buf_addch(buf, ' '); |
127 | |
|
128 | 0 | clauses--; |
129 | 0 | } |
130 | | |
131 | 0 | buf_addch(buf, ')'); |
132 | 0 | return true; |
133 | 0 | } |
134 | | |
135 | | /** |
136 | | * compile_search_self - Compile a search command for a pattern |
137 | | * @param adata Imap Account data |
138 | | * @param pat Pattern |
139 | | * @param buf Buffer for the resulting command |
140 | | * @retval true Success |
141 | | * @retval false Failure |
142 | | */ |
143 | | static bool compile_search_self(const struct ImapAccountData *adata, |
144 | | const struct Pattern *pat, struct Buffer *buf) |
145 | 0 | { |
146 | 0 | char term[256] = { 0 }; |
147 | 0 | char *delim = NULL; |
148 | |
|
149 | 0 | switch (pat->op) |
150 | 0 | { |
151 | 0 | case MUTT_PAT_HEADER: |
152 | 0 | buf_addstr(buf, "HEADER "); |
153 | | |
154 | | /* extract header name */ |
155 | 0 | delim = strchr(pat->p.str, ':'); |
156 | 0 | if (!delim) |
157 | 0 | { |
158 | 0 | mutt_error(_("Header search without header name: %s"), pat->p.str); |
159 | 0 | return false; |
160 | 0 | } |
161 | 0 | *delim = '\0'; |
162 | 0 | imap_quote_string(term, sizeof(term), pat->p.str, false); |
163 | 0 | buf_addstr(buf, term); |
164 | 0 | buf_addch(buf, ' '); |
165 | | |
166 | | /* and field */ |
167 | 0 | *delim = ':'; |
168 | 0 | delim++; |
169 | 0 | SKIPWS(delim); |
170 | 0 | imap_quote_string(term, sizeof(term), delim, false); |
171 | 0 | buf_addstr(buf, term); |
172 | 0 | break; |
173 | 0 | case MUTT_PAT_BODY: |
174 | 0 | buf_addstr(buf, "BODY "); |
175 | 0 | imap_quote_string(term, sizeof(term), pat->p.str, false); |
176 | 0 | buf_addstr(buf, term); |
177 | 0 | break; |
178 | 0 | case MUTT_PAT_WHOLE_MSG: |
179 | 0 | buf_addstr(buf, "TEXT "); |
180 | 0 | imap_quote_string(term, sizeof(term), pat->p.str, false); |
181 | 0 | buf_addstr(buf, term); |
182 | 0 | break; |
183 | 0 | case MUTT_PAT_SERVERSEARCH: |
184 | 0 | if (!(adata->capabilities & IMAP_CAP_X_GM_EXT_1)) |
185 | 0 | { |
186 | 0 | mutt_error(_("Server-side custom search not supported: %s"), pat->p.str); |
187 | 0 | return false; |
188 | 0 | } |
189 | 0 | buf_addstr(buf, "X-GM-RAW "); |
190 | 0 | imap_quote_string(term, sizeof(term), pat->p.str, false); |
191 | 0 | buf_addstr(buf, term); |
192 | 0 | break; |
193 | 0 | } |
194 | 0 | return true; |
195 | 0 | } |
196 | | |
197 | | /** |
198 | | * compile_search - Convert NeoMutt pattern to IMAP search |
199 | | * @param adata Imap Account data |
200 | | * @param pat Pattern to convert |
201 | | * @param buf Buffer for result |
202 | | * @retval true Success |
203 | | * @retval false Failure |
204 | | * |
205 | | * Convert neomutt Pattern to IMAP SEARCH command containing only elements |
206 | | * that require full-text search (neomutt already has what it needs for most |
207 | | * match types, and does a better job (eg server doesn't support regexes). |
208 | | */ |
209 | | static bool compile_search(const struct ImapAccountData *adata, |
210 | | const struct Pattern *pat, struct Buffer *buf) |
211 | 0 | { |
212 | 0 | if (!check_pattern(pat)) |
213 | 0 | return true; |
214 | | |
215 | 0 | if (pat->pat_not) |
216 | 0 | buf_addstr(buf, "NOT "); |
217 | |
|
218 | 0 | return pat->child ? compile_search_children(adata, pat, buf) : |
219 | 0 | compile_search_self(adata, pat, buf); |
220 | 0 | } |
221 | | |
222 | | /** |
223 | | * imap_search - Find messages in mailbox matching a pattern |
224 | | * @param m Mailbox |
225 | | * @param pat Pattern to match |
226 | | * @retval true Success |
227 | | * @retval false Failure |
228 | | */ |
229 | | bool imap_search(struct Mailbox *m, const struct PatternList *pat) |
230 | 0 | { |
231 | 0 | for (int i = 0; i < m->msg_count; i++) |
232 | 0 | { |
233 | 0 | struct Email *e = m->emails[i]; |
234 | 0 | if (!e) |
235 | 0 | break; |
236 | 0 | e->matched = false; |
237 | 0 | } |
238 | |
|
239 | 0 | if (check_pattern_list(pat) == 0) |
240 | 0 | return true; |
241 | | |
242 | 0 | struct Buffer buf; |
243 | 0 | buf_init(&buf); |
244 | 0 | buf_addstr(&buf, "UID SEARCH "); |
245 | |
|
246 | 0 | struct ImapAccountData *adata = imap_adata_get(m); |
247 | 0 | const bool ok = compile_search(adata, SLIST_FIRST(pat), &buf) && |
248 | 0 | (imap_exec(adata, buf.data, IMAP_CMD_NO_FLAGS) == IMAP_EXEC_SUCCESS); |
249 | |
|
250 | 0 | FREE(&buf.data); |
251 | 0 | return ok; |
252 | 0 | } |
253 | | |
254 | | /** |
255 | | * cmd_parse_search - Store SEARCH response for later use |
256 | | * @param adata Imap Account data |
257 | | * @param s Command string with search results |
258 | | */ |
259 | | void cmd_parse_search(struct ImapAccountData *adata, const char *s) |
260 | 0 | { |
261 | 0 | unsigned int uid; |
262 | 0 | struct Email *e = NULL; |
263 | 0 | struct ImapMboxData *mdata = adata->mailbox->mdata; |
264 | |
|
265 | 0 | mutt_debug(LL_DEBUG2, "Handling SEARCH\n"); |
266 | |
|
267 | 0 | while ((s = imap_next_word((char *) s)) && (*s != '\0')) |
268 | 0 | { |
269 | 0 | if (!mutt_str_atoui(s, &uid)) |
270 | 0 | continue; |
271 | 0 | e = mutt_hash_int_find(mdata->uid_hash, uid); |
272 | 0 | if (e) |
273 | 0 | e->matched = true; |
274 | 0 | } |
275 | 0 | } |