Coverage Report

Created: 2026-10-17 06:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib-mail/message-parser-from-parts.c
Line
Count
Source
1
/* Copyright (c) Dovecot authors, see top-level COPYING file */
2
3
#include "lib.h"
4
#include "istream.h"
5
#include "message-parser-private.h"
6
7
static int preparsed_parse_epilogue_init(struct message_parser_ctx *ctx,
8
           struct message_block *block_r);
9
static int preparsed_parse_next_header_init(struct message_parser_ctx *ctx,
10
              struct message_block *block_r);
11
12
static int preparsed_parse_eof(struct message_parser_ctx *ctx ATTR_UNUSED,
13
             struct message_block *block_r ATTR_UNUSED)
14
0
{
15
0
  return -1;
16
0
}
17
18
static void preparsed_skip_to_next(struct message_parser_ctx *ctx)
19
0
{
20
0
  ctx->parse_next_block = preparsed_parse_next_header_init;
21
0
  while (ctx->part != NULL) {
22
0
    if (ctx->part->next != NULL) {
23
0
      ctx->part = ctx->part->next;
24
0
      break;
25
0
    }
26
27
    /* parse epilogue of multipart parent if requested */
28
0
    if (ctx->part->parent != NULL &&
29
0
        (ctx->part->parent->flags & MESSAGE_PART_FLAG_MULTIPART) != 0 &&
30
0
        (ctx->flags & MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS) != 0) {
31
      /* check for presence of epilogue */
32
0
      uoff_t part_end = ctx->part->physical_pos +
33
0
        ctx->part->header_size.physical_size +
34
0
        ctx->part->body_size.physical_size;
35
0
      uoff_t parent_end = ctx->part->parent->physical_pos +
36
0
        ctx->part->parent->header_size.physical_size +
37
0
        ctx->part->parent->body_size.physical_size;
38
39
0
      if (parent_end > part_end) {
40
0
        ctx->parse_next_block = preparsed_parse_epilogue_init;
41
0
        break;
42
0
      }
43
0
    }
44
0
    ctx->part = ctx->part->parent;
45
0
  }
46
0
  if (ctx->part == NULL)
47
0
    ctx->parse_next_block = preparsed_parse_eof;
48
0
}
49
50
static int preparsed_parse_body_finish(struct message_parser_ctx *ctx,
51
               struct message_block *block_r)
52
0
{
53
0
  i_stream_skip(ctx->input, ctx->skip);
54
0
  ctx->skip = 0;
55
56
0
  preparsed_skip_to_next(ctx);
57
0
  return ctx->parse_next_block(ctx, block_r);
58
0
}
59
60
static int preparsed_parse_prologue_finish(struct message_parser_ctx *ctx,
61
             struct message_block *block_r)
62
0
{
63
0
  i_stream_skip(ctx->input, ctx->skip);
64
0
  ctx->skip = 0;
65
66
0
  ctx->parse_next_block = preparsed_parse_next_header_init;
67
0
  ctx->part = ctx->part->children;
68
0
  return ctx->parse_next_block(ctx, block_r);
69
0
}
70
71
static int preparsed_parse_body_more(struct message_parser_ctx *ctx,
72
             struct message_block *block_r)
73
0
{
74
0
  uoff_t end_offset = ctx->part->physical_pos +
75
0
    ctx->part->header_size.physical_size +
76
0
    ctx->part->body_size.physical_size;
77
0
  bool full;
78
0
  int ret;
79
80
0
  if ((ret = message_parser_read_more(ctx, block_r, &full)) <= 0)
81
0
    return ret;
82
83
0
  if (ctx->input->v_offset + block_r->size >= end_offset) {
84
0
    block_r->size = end_offset - ctx->input->v_offset;
85
0
    ctx->parse_next_block = preparsed_parse_body_finish;
86
0
  }
87
0
  ctx->skip = block_r->size;
88
0
  return 1;
89
0
}
90
91
static int preparsed_parse_prologue_more(struct message_parser_ctx *ctx,
92
           struct message_block *block_r)
93
0
{
94
0
  uoff_t boundary_min_start, end_offset;
95
0
  const unsigned char *cur;
96
0
  bool full;
97
0
  int ret;
98
99
0
  i_assert(ctx->part->children != NULL);
100
0
  end_offset = ctx->part->children->physical_pos;
101
102
0
  if ((ret = message_parser_read_more(ctx, block_r, &full)) <= 0)
103
0
    return ret;
104
105
0
  if (ctx->input->v_offset + block_r->size >= end_offset) {
106
    /* we've got the full prologue: clip off the initial boundary */
107
0
    block_r->size = end_offset - ctx->input->v_offset;
108
0
    cur = block_r->data + block_r->size - 1;
109
110
    /* [\r]\n--boundary[\r]\n */
111
0
    if (block_r->size < 5 || *cur != '\n') {
112
0
      ctx->broken_reason = "Prologue boundary end not at expected position";
113
0
      return -1;
114
0
    }
115
116
0
    cur--;
117
0
    if (*cur == '\r') cur--;
118
119
    /* find newline just before boundary */
120
0
    for (; cur >= block_r->data; cur--) {
121
0
      if (*cur == '\n') break;
122
0
    }
123
124
0
    if (cur < block_r->data) {
125
      /* boundary is at the very start of the prologue, i.e.
126
         there is no preamble before it (no preceding newline
127
         to find). Don't read before block_r->data. */
128
0
      if (block_r->data[0] != '-' || block_r->data[1] != '-') {
129
0
        ctx->broken_reason = "Prologue boundary beginning not at expected position";
130
0
        return -1;
131
0
      }
132
0
      block_r->size = 0;
133
0
    } else {
134
0
      if (cur[0] != '\n' || cur[1] != '-' || cur[2] != '-') {
135
0
        ctx->broken_reason = "Prologue boundary beginning not at expected position";
136
0
        return -1;
137
0
      }
138
139
0
      if (cur != block_r->data && cur[-1] == '\r') cur--;
140
141
      /* clip boundary */
142
0
      block_r->size = cur - block_r->data;
143
0
    }
144
145
0
    ctx->parse_next_block = preparsed_parse_prologue_finish;
146
0
    ctx->skip = block_r->size;
147
0
    return 1;
148
0
  }
149
150
  /* retain enough data in the stream buffer to contain initial boundary */
151
0
  if (end_offset > BOUNDARY_END_MAX_LEN)
152
0
    boundary_min_start = end_offset - BOUNDARY_END_MAX_LEN;
153
0
  else
154
0
    boundary_min_start = 0;
155
156
0
  if (ctx->input->v_offset + block_r->size >= boundary_min_start) {
157
0
    if (boundary_min_start <= ctx->input->v_offset)
158
0
      return 0;
159
0
    block_r->size = boundary_min_start - ctx->input->v_offset;
160
0
  }
161
0
  ctx->skip = block_r->size;
162
0
  return 1;
163
0
}
164
165
static int preparsed_parse_epilogue_more(struct message_parser_ctx *ctx,
166
           struct message_block *block_r)
167
0
{
168
0
  uoff_t end_offset = ctx->part->physical_pos +
169
0
    ctx->part->header_size.physical_size +
170
0
    ctx->part->body_size.physical_size;
171
0
  bool full;
172
0
  int ret;
173
174
0
  if ((ret = message_parser_read_more(ctx, block_r, &full)) <= 0)
175
0
    return ret;
176
177
0
  if (ctx->input->v_offset + block_r->size >= end_offset) {
178
0
    block_r->size = end_offset - ctx->input->v_offset;
179
0
    ctx->parse_next_block = preparsed_parse_body_finish;
180
0
  }
181
0
  ctx->skip = block_r->size;
182
0
  return 1;
183
0
}
184
185
static int
186
preparsed_parse_epilogue_skip_boundary_line(struct message_parser_ctx *ctx,
187
              struct message_block *block_r)
188
0
{
189
0
  uoff_t end_offset = ctx->part->physical_pos +
190
0
    ctx->part->header_size.physical_size +
191
0
    ctx->part->body_size.physical_size;
192
0
  const unsigned char *ptr;
193
0
  bool full;
194
0
  int ret;
195
196
0
  if ((ret = message_parser_read_more(ctx, block_r, &full)) <= 0)
197
0
    return ret;
198
199
  /* The boundary line is longer than the buffer could hold at once.
200
     Keep skipping until its end, the same way the forward parser's
201
     parse_next_body_skip_boundary_line() does. */
202
0
  ptr = memchr(block_r->data, '\n', block_r->size);
203
0
  ctx->skip = ptr != NULL ?
204
0
    (size_t)(ptr - block_r->data) + 1 : block_r->size;
205
206
0
  if (end_offset < ctx->input->v_offset + ctx->skip) {
207
0
    ctx->broken_reason = "Epilogue boundary end not at expected position";
208
0
    return -1;
209
0
  }
210
211
0
  if (ptr != NULL)
212
0
    ctx->parse_next_block = preparsed_parse_epilogue_more;
213
0
  block_r->size = 0;
214
0
  return 0;
215
0
}
216
217
static int preparsed_parse_epilogue_boundary(struct message_parser_ctx *ctx,
218
               struct message_block *block_r)
219
0
{
220
0
  uoff_t end_offset = ctx->part->physical_pos +
221
0
    ctx->part->header_size.physical_size +
222
0
    ctx->part->body_size.physical_size;
223
0
  const unsigned char *data, *cur;
224
0
  size_t size;
225
0
  bool full;
226
0
  int ret;
227
228
0
  if (end_offset - ctx->input->v_offset < 7) {
229
0
    ctx->broken_reason = "Epilogue position is wrong";
230
0
    return -1;
231
0
  }
232
233
0
  if ((ret = message_parser_read_more(ctx, block_r, &full)) <= 0)
234
0
    return ret;
235
236
  /* [\r]\n--boundary--[\r]\n */
237
0
  if (block_r->size < 7) {
238
0
    ctx->want_count = 7;
239
0
    return 0;
240
0
  }
241
242
0
  data = block_r->data;
243
0
  size = block_r->size;
244
0
  cur = data;
245
246
0
  if (*cur == '\r') cur++;
247
248
0
  if (cur[0] != '\n' || cur[1] != '-' || cur[2] != '-') {
249
0
    ctx->broken_reason = "Epilogue boundary start not at expected position";
250
0
    return -1;
251
0
  }
252
253
  /* find the end of the line */
254
0
  cur += 3;
255
0
  if ((cur = memchr(cur, '\n', size - (cur-data))) == NULL) {
256
0
    if (end_offset < ctx->input->v_offset + size) {
257
0
      ctx->broken_reason = "Epilogue boundary end not at expected position";
258
0
      return -1;
259
0
    } else if (ctx->input->v_offset + size < end_offset &&
260
0
         size < BOUNDARY_END_MAX_LEN &&
261
0
         !ctx->input->eof && !full) {
262
0
      ctx->want_count = BOUNDARY_END_MAX_LEN;
263
0
      return 0;
264
0
    }
265
    /* The line's end isn't in the data we have and buffering more
266
       of it won't help. Skip what we have and keep looking for the
267
       end of the line. */
268
0
    block_r->size = 0;
269
0
    ctx->parse_next_block =
270
0
      preparsed_parse_epilogue_skip_boundary_line;
271
0
    ctx->skip = size;
272
0
    return 0;
273
0
  }
274
275
0
  block_r->size = 0;
276
0
  ctx->parse_next_block = preparsed_parse_epilogue_more;
277
0
  ctx->skip = cur - data + 1;
278
0
  return 0;
279
0
}
280
281
static int preparsed_parse_body_init(struct message_parser_ctx *ctx,
282
             struct message_block *block_r)
283
0
{
284
0
  uoff_t offset = ctx->part->physical_pos +
285
0
    ctx->part->header_size.physical_size;
286
287
0
  if (offset < ctx->input->v_offset) {
288
    /* header was actually larger than the cached size suggested */
289
0
    ctx->broken_reason = "Header larger than its cached size";
290
0
    return -1;
291
0
  }
292
0
  i_stream_skip(ctx->input, offset - ctx->input->v_offset);
293
294
  /* multipart messages may begin with --boundary--, which makes them
295
     not have any children. */
296
0
  if ((ctx->part->flags & MESSAGE_PART_FLAG_MULTIPART) == 0 ||
297
0
      ctx->part->children == NULL)
298
0
    ctx->parse_next_block = preparsed_parse_body_more;
299
0
  else
300
0
    ctx->parse_next_block = preparsed_parse_prologue_more;
301
0
  return ctx->parse_next_block(ctx, block_r);
302
0
}
303
304
static int preparsed_parse_epilogue_init(struct message_parser_ctx *ctx,
305
           struct message_block *block_r)
306
0
{
307
0
  uoff_t offset = ctx->part->physical_pos +
308
0
    ctx->part->header_size.physical_size +
309
0
    ctx->part->body_size.physical_size;
310
311
0
  ctx->part = ctx->part->parent;
312
313
0
  if (offset < ctx->input->v_offset) {
314
    /* last child was actually larger than the cached size
315
       suggested */
316
0
    ctx->broken_reason = "Part larger than its cached size";
317
0
    return -1;
318
0
  }
319
0
  i_stream_skip(ctx->input, offset - ctx->input->v_offset);
320
321
0
  ctx->parse_next_block = preparsed_parse_epilogue_boundary;
322
0
  return ctx->parse_next_block(ctx, block_r);
323
0
}
324
325
static int preparsed_parse_finish_header(struct message_parser_ctx *ctx,
326
           struct message_block *block_r)
327
0
{
328
0
  if (ctx->part->children != NULL) {
329
0
    if ((ctx->part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0 &&
330
0
        (ctx->flags & MESSAGE_PARSER_FLAG_INCLUDE_MULTIPART_BLOCKS) != 0)
331
0
      ctx->parse_next_block = preparsed_parse_body_init;
332
0
    else {
333
0
      ctx->parse_next_block = preparsed_parse_next_header_init;
334
0
      ctx->part = ctx->part->children;
335
0
    }
336
0
  } else if ((ctx->flags & MESSAGE_PARSER_FLAG_SKIP_BODY_BLOCK) == 0) {
337
0
    ctx->parse_next_block = preparsed_parse_body_init;
338
0
  } else {
339
0
    preparsed_skip_to_next(ctx);
340
0
  }
341
0
  return ctx->parse_next_block(ctx, block_r);
342
0
}
343
344
static int preparsed_parse_next_header(struct message_parser_ctx *ctx,
345
               struct message_block *block_r)
346
0
{
347
0
  struct message_header_line *hdr;
348
0
  int ret;
349
350
0
  ret = message_parse_header_next(ctx->hdr_parser_ctx, &hdr);
351
0
  if (ret == 0 || (ret < 0 && ctx->input->stream_errno != 0)) {
352
0
    ctx->want_count = i_stream_get_data_size(ctx->input) + 1;
353
0
    return ret;
354
0
  }
355
356
0
  if (hdr != NULL) {
357
0
    if (!hdr->continues) {
358
0
      ctx->all_headers_total_size += hdr->name_len;
359
0
      ctx->all_headers_total_size += hdr->middle_len;
360
0
    }
361
0
    ctx->all_headers_total_size += hdr->value_len;
362
0
    block_r->hdr = hdr;
363
0
    block_r->size = 0;
364
0
    return 1;
365
0
  }
366
0
  message_parse_header_deinit(&ctx->hdr_parser_ctx);
367
368
0
  ctx->parse_next_block = preparsed_parse_finish_header;
369
370
  /* return empty block as end of headers */
371
0
  block_r->hdr = NULL;
372
0
  block_r->size = 0;
373
374
0
  i_assert(ctx->skip == 0);
375
0
  if (ctx->input->v_offset != ctx->part->physical_pos +
376
0
      ctx->part->header_size.physical_size) {
377
0
    ctx->broken_reason = "Cached header size mismatch";
378
0
    return -1;
379
0
  }
380
0
  return 1;
381
0
}
382
383
static int preparsed_parse_next_header_init(struct message_parser_ctx *ctx,
384
              struct message_block *block_r)
385
0
{
386
0
  struct istream *hdr_input;
387
388
0
  i_assert(ctx->hdr_parser_ctx == NULL);
389
390
0
  if (ctx->part->physical_pos < ctx->input->v_offset) {
391
    /* The cached message_part tree is internally inconsistent:
392
       the previous part's size overlaps into this part's
393
       position. */
394
0
    ctx->broken_reason = "Part starts before its cached position";
395
0
    return -1;
396
0
  }
397
0
  i_stream_skip(ctx->input, ctx->part->physical_pos -
398
0
          ctx->input->v_offset);
399
400
  /* the header may become truncated by --boundaries. limit the header
401
     stream's size to what it's supposed to be to avoid duplicating (and
402
     keeping in sync!) all the same complicated logic as in
403
     parse_next_header(). */
404
0
  hdr_input = i_stream_create_limit(ctx->input, ctx->part->header_size.physical_size);
405
0
  ctx->hdr_parser_ctx =
406
0
    message_parse_header_init(hdr_input, NULL, ctx->hdr_flags);
407
0
  i_stream_unref(&hdr_input);
408
409
0
  size_t headers_available =
410
0
    ctx->all_headers_max_size > ctx->all_headers_total_size ?
411
0
    ctx->all_headers_max_size - ctx->all_headers_total_size : 0;
412
0
  message_parse_header_lower_limit(ctx->hdr_parser_ctx, headers_available);
413
414
0
  ctx->parse_next_block = preparsed_parse_next_header;
415
0
  return preparsed_parse_next_header(ctx, block_r);
416
0
}
417
418
struct message_parser_ctx *
419
message_parser_init_from_parts(struct message_part *parts,
420
             struct istream *input,
421
             const struct message_parser_settings *set)
422
0
{
423
0
  struct message_parser_ctx *ctx;
424
425
0
  i_assert(parts != NULL);
426
427
0
  ctx = message_parser_init_int(input, set);
428
0
  ctx->preparsed = TRUE;
429
0
  ctx->parts = ctx->part = parts;
430
0
  ctx->parse_next_block = preparsed_parse_next_header_init;
431
0
  return ctx;
432
0
}