• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

neomutt / neomutt / 24648356273

18 Apr 2026 01:24PM UTC coverage: 42.851% (+0.5%) from 42.375%
24648356273

push

github

flatcap
merge: fix security issues

Raised by evilrabbit on Mutt devel mailing list.

 * security: fix GSSAPI buffer underflow on short unwrapped tokens
 * security: reject percent-encoded NUL bytes in URL decoding
 * security: skip CN fallback when SAN dNSName entries exist (RFC6125)
 * security: cap POP3 UIDL responses to prevent OOM from malicious server

3 of 7 new or added lines in 2 files covered. (42.86%)

3465 existing lines in 53 files now uncovered.

12428 of 29003 relevant lines covered (42.85%)

5272.14 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.43
/expando/node_condition.c
1
/**
2
 * @file
3
 * Expando Node for a Condition
4
 *
5
 * @authors
6
 * Copyright (C) 2023-2024 Tóth János <gomba007@gmail.com>
7
 * Copyright (C) 2023-2024 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 expando_node_condition Condition Node
26
 *
27
 * Expando Node for a Condition
28
 */
29

30
#include "config.h"
31
#include <stdbool.h>
32
#include <stdio.h>
33
#include <string.h>
34
#include "mutt/lib.h"
35
#include "node_condition.h"
36
#include "definition.h"
37
#include "format.h"
38
#include "helpers.h"
39
#include "node.h"
40
#include "node_condbool.h"
41
#include "node_container.h"
42
#include "node_expando.h"
43
#include "node_text.h"
44
#include "parse.h"
45
#include "render.h"
46

47
/**
48
 * node_condition_render - Render a Conditional Node - Implements ExpandoNode::render() - @ingroup expando_render
49
 */
50
static int node_condition_render(const struct ExpandoNode *node,
21✔
51
                                 const struct ExpandoRenderCallback *erc,
52
                                 struct Buffer *buf, int max_cols, void *data,
53
                                 MuttFormatFlags flags)
54
{
55
  ASSERT(node->type == ENT_CONDITION);
21✔
56

57
  const struct ExpandoNode *node_cond = node_get_child(node, ENC_CONDITION);
21✔
58

59
  // Discard any text returned, just use the return value as a bool
60
  struct Buffer *buf_cond = buf_pool_get();
21✔
61
  int rc_cond = node_cond->render(node_cond, erc, buf_cond, max_cols, data, flags);
21✔
62

63
  int rc = 0;
64
  buf_reset(buf_cond);
21✔
65

66
  if (rc_cond == true)
21✔
67
  {
68
    const struct ExpandoNode *node_true = node_get_child(node, ENC_TRUE);
12✔
69
    rc = node_render(node_true, erc, buf_cond, max_cols, data, flags);
12✔
70
  }
71
  else
72
  {
73
    const struct ExpandoNode *node_false = node_get_child(node, ENC_FALSE);
9✔
74
    rc = node_render(node_false, erc, buf_cond, max_cols, data, flags);
9✔
75
  }
76

77
  const struct ExpandoFormat *fmt = node->format;
21✔
78
  if (!fmt)
21✔
79
  {
80
    buf_addstr(buf, buf_string(buf_cond));
36✔
81
    buf_pool_release(&buf_cond);
18✔
82
    return rc;
18✔
83
  }
84

85
  struct Buffer *tmp = buf_pool_get();
3✔
86

87
  int min_cols = MAX(fmt->min_cols, fmt->max_cols);
3✔
88
  min_cols = MIN(min_cols, max_cols);
3✔
89
  if (fmt->max_cols >= 0)
3✔
90
    max_cols = MIN(max_cols, fmt->max_cols);
2✔
91
  rc = format_string(tmp, min_cols, max_cols, fmt->justification, ' ',
6✔
92
                     buf_string(buf_cond), buf_len(buf_cond), true);
93
  if (fmt->lower)
3✔
94
    buf_lower_special(tmp);
1✔
95

96
  buf_addstr(buf, buf_string(tmp));
6✔
97
  buf_pool_release(&tmp);
3✔
98
  buf_pool_release(&buf_cond);
3✔
99

100
  return rc;
3✔
101
}
102

103
/**
104
 * node_condition_new - Create a new Condition Expando Node
105
 * @param node_cond  Expando Node that will be tested
106
 * @param node_true  Node tree for the 'true' case
107
 * @param node_false Node tree for the 'false' case
108
 * @param fmt        Formatting info
109
 * @retval ptr New Condition Expando Node
110
 */
111
struct ExpandoNode *node_condition_new(struct ExpandoNode *node_cond,
18,289✔
112
                                       struct ExpandoNode *node_true,
113
                                       struct ExpandoNode *node_false,
114
                                       struct ExpandoFormat *fmt)
115
{
116
  ASSERT(node_cond);
18,289✔
117

118
  struct ExpandoNode *node = node_new();
18,289✔
119

120
  node->type = ENT_CONDITION;
18,289✔
121
  node->render = node_condition_render;
18,289✔
122

123
  ARRAY_SET(&node->children, ENC_CONDITION, node_cond);
18,289✔
124
  ARRAY_SET(&node->children, ENC_TRUE, node_true);
18,289✔
125
  ARRAY_SET(&node->children, ENC_FALSE, node_false);
18,289✔
126

127
  node->format = fmt;
18,289✔
128

129
  return node;
18,289✔
130
}
131

132
/**
133
 * node_condition_parse - Parse a conditional Expando
134
 * @param[in]  str          String to parse
135
 * @param[in]  term_chars   Terminator characters, e.g. #NTE_GREATER
136
 * @param[in]  defs         Expando definitions
137
 * @param[out] parsed_until First character after parsed string
138
 * @param[out] err          Buffer for errors
139
 * @retval ptr Expando Node
140
 */
141
struct ExpandoNode *node_condition_parse(const char *str, NodeTextTermFlags term_chars,
98,745✔
142
                                         const struct ExpandoDefinition *defs,
143
                                         const char **parsed_until,
144
                                         struct ExpandoParseError *err)
145
{
146
  if (!str || (str[0] != '%'))
98,745✔
147
    return NULL;
148

149
  str++; // Skip %
98,742✔
150

151
  struct ExpandoFormat *fmt = NULL;
98,742✔
152
  struct ExpandoNode *node_cond = NULL;
98,742✔
153
  struct ExpandoNode *node_true = NULL;
98,742✔
154
  struct ExpandoNode *node_false = NULL;
98,742✔
155

156
  //----------------------------------------------------------------------------
157
  // Parse the format (optional)
158
  fmt = parse_format(str, parsed_until, err);
98,742✔
159
  if (err->position)
98,742✔
160
    goto fail;
5✔
161

162
  str = *parsed_until;
98,737✔
163

164
  if ((str[0] != '<') && (str[0] != '?'))
98,737✔
165
    goto fail;
80,427✔
166

167
  const bool old_style = (str[0] == '?'); // %?X?...&...?
168
  str++;
18,310✔
169

170
  //----------------------------------------------------------------------------
171
  // Parse the condition
172
  // Try long name first if it looks like %<{NAME}?...>
173
  if (str[0] == '{')
18,310✔
174
  {
175
    node_cond = parse_long_name(str + 1, defs, EP_CONDITIONAL, NULL, parsed_until, err);
15✔
176
    if (node_cond)
15✔
177
    {
178
      if ((*parsed_until)[0] != '}')
10✔
179
      {
180
        err->position = *parsed_until;
×
181
        snprintf(err->message, sizeof(err->message), _("Expando is missing closing '}'"));
×
UNCOV
182
        goto fail;
×
183
      }
184
      (*parsed_until)++; // Skip the '}'
10✔
185
    }
186
    else if (err->position)
5✔
187
    {
UNCOV
188
      goto fail;
×
189
    }
190
    else
191
    {
192
      // Report an error if the content looks like a long name
193
      const char *name_start = str + 1;
194
      const char *end = name_start + strspn(name_start, "abcdefghijklmnopqrstuvwxyz0123456789-");
5✔
195
      if (end != name_start)
5✔
196
      {
197
        err->position = name_start;
4✔
198
        if (*end != '}')
4✔
199
          snprintf(err->message, sizeof(err->message), _("Expando is missing closing '}'"));
2✔
200
        else
201
          // L10N: e.g. "Unknown expando: %{bad}"
202
          snprintf(err->message, sizeof(err->message), _("Unknown expando: %%{%.*s}"),
2✔
203
                   (int) (end - name_start), name_start);
204
        goto fail;
4✔
205
      }
206
      // Doesn't look like a long name, fall through to parse_short_name
207
    }
208
  }
209

210
  if (!node_cond)
18,306✔
211
  {
212
    node_cond = parse_short_name(str, defs, EP_CONDITIONAL, NULL, parsed_until, err);
18,296✔
213
    if (!node_cond)
18,296✔
214
      goto fail;
6✔
215
  }
216

217
  if (node_cond->type == ENT_EXPANDO)
18,300✔
218
  {
219
    node_cond->type = ENT_CONDBOOL;
18,283✔
220
    node_cond->render = node_condbool_render;
18,283✔
221
  }
222

223
  str = *parsed_until; // Skip the expando
18,300✔
224
  if (str[0] != '?')
18,300✔
225
  {
226
    err->position = str;
1✔
227
    snprintf(err->message, sizeof(err->message),
1✔
228
             // L10N: Expando is missing a terminator character
229
             //       e.g. "%[..." is missing the final ']'
230
             _("Conditional expando is missing '%c'"), '?');
1✔
231
    goto fail;
1✔
232
  }
233
  str++; // Skip the '?'
18,299✔
234

235
  //----------------------------------------------------------------------------
236
  // Parse the 'true' clause (optional)
237
  const NodeTextTermFlags term_true = term_chars | NTE_AMPERSAND |
18,299✔
238
                                      (old_style ? NTE_QUESTION : NTE_GREATER);
239

240
  node_true = node_container_new();
18,299✔
241
  node_parse_many(node_true, str, term_true, defs, parsed_until, err);
18,299✔
242
  if (err->position)
18,299✔
243
    goto fail;
2✔
244

245
  str = *parsed_until;
18,297✔
246

247
  //----------------------------------------------------------------------------
248
  // Parse the 'false' clause (optional)
249

250
  node_false = NULL;
18,297✔
251
  if (str[0] == '&')
18,297✔
252
  {
253
    str++;
3,976✔
254
    const NodeTextTermFlags term_false = term_chars | (old_style ? NTE_QUESTION : NTE_GREATER);
3,976✔
255

256
    node_false = node_container_new();
3,976✔
257
    node_parse_many(node_false, str, term_false, defs, parsed_until, err);
3,976✔
258
    if (err->position)
3,976✔
259
      goto fail;
2✔
260

261
    str = *parsed_until;
3,974✔
262
  }
263

264
  //----------------------------------------------------------------------------
265
  // Check for the terminator character
266
  const char terminator = old_style ? '?' : '>';
18,295✔
267

268
  if (str[0] != terminator)
18,295✔
269
  {
270
    err->position = str;
6✔
271
    snprintf(err->message, sizeof(err->message),
6✔
272
             // L10N: Expando is missing a terminator character
273
             //       e.g. "%[..." is missing the final ']'
274
             _("Conditional expando is missing '%c'"), '?');
6✔
275
    goto fail;
6✔
276
  }
277

278
  *parsed_until = str + 1;
18,289✔
279

280
  return node_condition_new(node_cond, node_true, node_false, fmt);
18,289✔
281

282
fail:
80,453✔
283
  FREE(&fmt);
80,453✔
284
  node_free(&node_cond);
80,453✔
285
  node_free(&node_true);
80,453✔
286
  node_free(&node_false);
80,453✔
287
  return NULL;
80,453✔
288
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc