Coverage Report

Created: 2023-09-25 07: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) 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
}