Coverage Report

Created: 2025-04-22 06:17

/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
}