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