Coverage Report

Created: 2026-05-30 06:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib-imap/imap-bodystructure.c
Line
Count
Source
1
/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
2
3
#include "lib.h"
4
#include "buffer.h"
5
#include "istream.h"
6
#include "str.h"
7
#include "message-part-data.h"
8
#include "message-parser.h"
9
#include "rfc822-parser.h"
10
#include "rfc2231-parser.h"
11
#include "imap-parser.h"
12
#include "imap-quote.h"
13
#include "imap-envelope.h"
14
#include "imap-bodystructure.h"
15
16
/* The max level of lists nesting inside the parenthesised representation of a
17
   single part with no other parts inside, i.e. the max level of list nesting
18
   in representing a single part. According to RFC-3501, this should be in the
19
   order of a couple of nestings only, let's keep some margin just in case. */
20
#define BODYSTRUCTURE_MAX_PARENTHESIS_NESTING \
21
29.2k
  (MESSAGE_PARSER_DEFAULT_MAX_NESTED_MIME_PARTS + 10)
22
23
0
#define EMPTY_BODY "(\"text\" \"plain\" " \
24
0
  "(\"charset\" \""MESSAGE_PART_DEFAULT_CHARSET"\") NIL NIL \"7bit\" 0 0)"
25
0
#define EMPTY_BODYSTRUCTURE "(\"text\" \"plain\" " \
26
0
  "(\"charset\" \""MESSAGE_PART_DEFAULT_CHARSET"\") NIL NIL \"7bit\" 0 0 " \
27
0
    "NIL NIL NIL NIL)"
28
29
/*
30
 * IMAP BODY/BODYSTRUCTURE write
31
 */
32
33
static void
34
params_write(const struct message_part_param *params, unsigned int params_count,
35
       string_t *str, bool default_charset, enum imap_quote_flags qflags)
36
12.8k
{
37
12.8k
  unsigned int i;
38
12.8k
  bool seen_charset;
39
40
12.8k
  if (!default_charset && params_count == 0) {
41
10.7k
    str_append(str, "NIL");
42
10.7k
    return;
43
10.7k
  }
44
2.11k
  str_append_c(str, '(');
45
46
2.11k
  seen_charset = FALSE;
47
587k
  for (i = 0; i < params_count; i++) {
48
585k
    i_assert(params[i].name != NULL);
49
585k
    i_assert(params[i].value != NULL);
50
51
585k
    if (i > 0)
52
583k
      str_append_c(str, ' ');
53
585k
    if (default_charset &&
54
9.66k
      strcasecmp(params[i].name, "charset") == 0)
55
387
      seen_charset = TRUE;
56
585k
    imap_append_string(str, params[i].name, qflags);
57
585k
    str_append_c(str, ' ');
58
585k
    imap_append_string(str, params[i].value, qflags);
59
585k
  }
60
2.11k
  if (default_charset && !seen_charset) {
61
799
    if (i > 0)
62
435
      str_append_c(str, ' ');
63
799
    str_append(str, "\"charset\" "
64
799
      "\""MESSAGE_PART_DEFAULT_CHARSET"\"");
65
799
  }
66
2.11k
  str_append_c(str, ')');
67
2.11k
}
68
69
static int
70
part_write_bodystructure_siblings(const struct message_part *part,
71
          string_t *dest, bool extended,
72
          enum imap_quote_flags qflags,
73
          const char **error_r)
74
6.15k
{
75
16.6k
  for (; part != NULL; part = part->next) {
76
10.4k
    str_append_c(dest, '(');
77
10.4k
    if (imap_bodystructure_write(part, dest, extended, qflags,
78
10.4k
               error_r) < 0)
79
0
      return -1;
80
10.4k
    str_append_c(dest, ')');
81
10.4k
  }
82
6.15k
  return 0;
83
6.15k
}
84
85
static void
86
part_write_bodystructure_common(const struct message_part_data *data,
87
        string_t *str, enum imap_quote_flags qflags)
88
12.3k
{
89
12.3k
  str_append_c(str, ' ');
90
12.3k
  if (data->content_disposition == NULL)
91
11.8k
    str_append(str, "NIL");
92
528
  else {
93
528
    str_append_c(str, '(');
94
528
    imap_append_string(str, data->content_disposition, qflags);
95
96
528
    str_append_c(str, ' ');
97
528
    params_write(data->content_disposition_params,
98
528
      data->content_disposition_params_count, str,
99
528
      FALSE, qflags);
100
101
528
    str_append_c(str, ')');
102
528
  }
103
104
12.3k
  str_append_c(str, ' ');
105
12.3k
  if (data->content_language == NULL)
106
11.6k
    str_append(str, "NIL");
107
748
  else {
108
748
    const char *const *lang = data->content_language;
109
110
748
    i_assert(*lang != NULL);
111
748
    str_append_c(str, '(');
112
748
    imap_append_string(str, *lang, qflags);
113
748
    lang++;
114
1.23M
    while (*lang != NULL) {
115
1.23M
      str_append_c(str, ' ');
116
1.23M
      imap_append_string(str, *lang, qflags);
117
1.23M
      lang++;
118
1.23M
    }
119
748
    str_append_c(str, ')');
120
748
  }
121
122
12.3k
  str_append_c(str, ' ');
123
12.3k
  imap_append_nstring_nolf(str, data->content_location, qflags);
124
12.3k
}
125
126
static int part_write_body_multipart(const struct message_part *part,
127
             string_t *str, bool extended,
128
             enum imap_quote_flags qflags,
129
             const char **error_r)
130
4.71k
{
131
4.71k
  const struct message_part_data *data = part->data;
132
133
4.71k
  i_assert(part->data != NULL);
134
135
4.71k
  if (part->children != NULL) {
136
4.71k
    if (part_write_bodystructure_siblings(part->children, str,
137
4.71k
                  extended, qflags,
138
4.71k
                  error_r) < 0)
139
0
      return -1;
140
4.71k
  } else {
141
    /* no parts in multipart message,
142
       that's not allowed. write a single
143
       0-length text/plain structure */
144
0
    if (!extended)
145
0
      str_append(str, EMPTY_BODY);
146
0
    else
147
0
      str_append(str, EMPTY_BODYSTRUCTURE);
148
0
  }
149
150
4.71k
  str_append_c(str, ' ');
151
4.71k
  imap_append_string(str, data->content_subtype, qflags);
152
153
4.71k
  if (!extended)
154
0
    return 0;
155
156
  /* BODYSTRUCTURE data */
157
158
4.71k
  str_append_c(str, ' ');
159
4.71k
  params_write(data->content_type_params,
160
4.71k
    data->content_type_params_count, str, FALSE, qflags);
161
162
4.71k
  part_write_bodystructure_common(data, str, qflags);
163
4.71k
  return 0;
164
4.71k
}
165
166
static bool part_is_truncated(const struct message_part *part)
167
6.20k
{
168
6.20k
  const struct message_part_data *data = part->data;
169
170
6.20k
  i_assert((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) == 0);
171
6.20k
  i_assert((part->flags & MESSAGE_PART_FLAG_MULTIPART) == 0);
172
173
6.20k
  if (data->content_type != NULL) {
174
6.20k
    if (strcasecmp(data->content_type, "message") == 0 &&
175
524
        strcasecmp(data->content_subtype, "rfc822") == 0) {
176
      /* It's message/rfc822, but without
177
         MESSAGE_PART_FLAG_MESSAGE_RFC822. */
178
0
      return TRUE;
179
0
    }
180
6.20k
    if (strcasecmp(data->content_type, "multipart") == 0) {
181
      /* It's multipart/, but without
182
         MESSAGE_PART_FLAG_MULTIPART. */
183
368
      return TRUE;
184
368
    }
185
6.20k
  } else {
186
    /* No Content-Type */
187
0
    if (part->parent != NULL &&
188
0
        (part->parent->flags & MESSAGE_PART_FLAG_MULTIPART_DIGEST) != 0) {
189
      /* Parent is MESSAGE_PART_FLAG_MULTIPART_DIGEST
190
         (so this should have been message/rfc822). */
191
0
      return TRUE;
192
0
    }
193
0
  }
194
5.83k
  return FALSE;
195
6.20k
}
196
197
static int
198
part_write_body(const struct message_part *part, string_t *str,
199
    bool extended, enum imap_quote_flags qflags,
200
    const char **error_r)
201
7.64k
{
202
7.64k
  const struct message_part_data *data = part->data;
203
7.64k
  bool text;
204
205
7.64k
  i_assert(part->data != NULL);
206
207
7.64k
  if ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0) {
208
1.43k
    str_append(str, "\"message\" \"rfc822\"");
209
1.43k
    text = FALSE;
210
6.20k
  } else if (part_is_truncated(part)) {
211
    /* Maximum MIME part count was reached while parsing the mail.
212
       Write this part out as application/octet-stream instead.
213
       We're not using text/plain, because it would require
214
       message-parser to use MESSAGE_PART_FLAG_TEXT for this part
215
       to avoid losing line count in message_part serialization. */
216
368
    str_append(str, "\"application\" \"octet-stream\"");
217
368
    text = FALSE;
218
5.83k
  } else {
219
    /* "content type" "subtype" */
220
5.83k
    if (data->content_type == NULL) {
221
0
      text = TRUE;
222
0
      str_append(str, "\"text\" \"plain\"");
223
5.83k
    } else {
224
5.83k
      text = (strcasecmp(data->content_type, "text") == 0);
225
5.83k
      imap_append_string(str, data->content_type, qflags);
226
5.83k
      str_append_c(str, ' ');
227
5.83k
      imap_append_string(str, data->content_subtype, qflags);
228
5.83k
    }
229
5.83k
    bool part_is_text = (part->flags & MESSAGE_PART_FLAG_TEXT) != 0;
230
5.83k
    if (text != part_is_text) {
231
0
      *error_r = "text flag mismatch";
232
0
      return -1;
233
0
    }
234
5.83k
  }
235
236
  /* ("content type param key" "value" ...) */
237
7.64k
  str_append_c(str, ' ');
238
7.64k
  params_write(data->content_type_params,
239
7.64k
    data->content_type_params_count, str, text, qflags);
240
241
7.64k
  str_append_c(str, ' ');
242
7.64k
  imap_append_nstring_nolf(str, data->content_id, qflags);
243
7.64k
  str_append_c(str, ' ');
244
7.64k
  imap_append_nstring_nolf(str, data->content_description, qflags);
245
7.64k
  str_append_c(str, ' ');
246
7.64k
  if (data->content_transfer_encoding != NULL)
247
7.64k
    imap_append_string(str, data->content_transfer_encoding, qflags);
248
0
  else
249
0
    str_append(str, "\"7bit\"");
250
7.64k
  str_printfa(str, " %"PRIuUOFF_T, part->body_size.virtual_size);
251
252
7.64k
  if (text) {
253
    /* text/.. contains line count */
254
1.00k
    str_printfa(str, " %u", part->body_size.lines);
255
6.64k
  } else if ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0) {
256
    /* message/rfc822 contains envelope + body + line count */
257
1.43k
    const struct message_part_data *child_data;
258
259
1.43k
    i_assert(part->children != NULL);
260
1.43k
    i_assert(part->children->next == NULL);
261
262
1.43k
    child_data = part->children->data;
263
264
1.43k
    str_append(str, " (");
265
1.43k
    imap_envelope_write(child_data->envelope, str, qflags);
266
1.43k
    str_append(str, ") ");
267
268
1.43k
    if (part_write_bodystructure_siblings(part->children, str,
269
1.43k
                  extended, qflags,
270
1.43k
                  error_r) < 0)
271
0
      return -1;
272
1.43k
    str_printfa(str, " %u", part->body_size.lines);
273
1.43k
  }
274
275
7.64k
  if (!extended)
276
0
    return 0;
277
278
  /* BODYSTRUCTURE data */
279
280
  /* "md5" ("content disposition" ("disposition" "params"))
281
     ("body" "language" "params") "location" */
282
7.64k
  str_append_c(str, ' ');
283
7.64k
  imap_append_nstring_nolf(str, data->content_md5, qflags);
284
7.64k
  part_write_bodystructure_common(data, str, qflags);
285
7.64k
  return 0;
286
7.64k
}
287
288
int imap_bodystructure_write(const struct message_part *part,
289
           string_t *dest, bool extended,
290
           enum imap_quote_flags qflags,
291
           const char **error_r)
292
12.3k
{
293
12.3k
  if ((part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0) {
294
4.71k
    return part_write_body_multipart(part, dest, extended, qflags,
295
4.71k
             error_r);
296
7.64k
  } else {
297
7.64k
    return part_write_body(part, dest, extended, qflags, error_r);
298
7.64k
  }
299
12.3k
}
300
301
/*
302
 * IMAP BODYSTRUCTURE parsing
303
 */
304
305
static int
306
imap_bodystructure_strlist_parse(const struct imap_arg *arg,
307
  pool_t pool, const char *const **list_r)
308
13.1k
{
309
13.1k
  const char *item, **list;
310
13.1k
  const struct imap_arg *list_args;
311
13.1k
  unsigned int list_count, i;
312
313
13.1k
  if (arg->type == IMAP_ARG_NIL) {
314
11.6k
    *list_r = NULL;
315
11.6k
    return 0;
316
11.6k
  }
317
1.52k
  if (imap_arg_get_nstring(arg, &item)) {
318
382
    list = p_new(pool, const char *, 2);
319
382
    list[0] = p_strdup(pool, item);
320
1.14k
  } else {
321
1.14k
    if (!imap_arg_get_list_full(arg, &list_args, &list_count))
322
3
      return -1;
323
1.14k
    if (list_count == 0)
324
1
      return -1;
325
326
1.14k
    list = p_new(pool, const char *, list_count+1);
327
2.46M
    for (i = 0; i < list_count; i++) {
328
2.46M
      if (!imap_arg_get_string(&list_args[i], &item))
329
16
        return -1;
330
2.46M
      list[i] = p_strdup(pool, item);
331
2.46M
    }
332
1.14k
  }
333
1.50k
  *list_r = list;
334
1.50k
  return 0;
335
1.52k
}
336
337
static int
338
imap_bodystructure_params_parse(const struct imap_arg *arg,
339
  pool_t pool, const struct message_part_param **params_r,
340
  unsigned int *count_r)
341
24.0k
{
342
24.0k
  struct message_part_param *params;
343
24.0k
  const struct imap_arg *list_args;
344
24.0k
  unsigned int list_count, params_count, i;
345
346
24.0k
  if (arg->type == IMAP_ARG_NIL) {
347
19.8k
    *params_r = NULL;
348
19.8k
    return 0;
349
19.8k
  }
350
4.26k
  if (!imap_arg_get_list_full(arg, &list_args, &list_count))
351
338
    return -1;
352
3.93k
  if ((list_count % 2) != 0)
353
4
    return -1;
354
3.92k
  if (list_count == 0)
355
13
    return -1;
356
357
3.91k
  params_count = list_count/2;
358
3.91k
  params = p_new(pool, struct message_part_param, params_count+1);
359
1.17M
  for (i = 0; i < params_count; i++) {
360
1.17M
    const char *name, *value;
361
362
1.17M
    if (!imap_arg_get_string(&list_args[i*2+0], &name))
363
2
      return -1;
364
1.17M
    if (!imap_arg_get_string(&list_args[i*2+1], &value))
365
9
      return -1;
366
1.17M
    params[i].name = p_strdup(pool, name);
367
1.17M
    params[i].value = p_strdup(pool, value);
368
1.17M
  }
369
3.90k
  *params_r = params;
370
3.90k
  *count_r = params_count;
371
3.90k
  return 0;
372
3.91k
}
373
374
static int
375
imap_bodystructure_parse_args_common(struct message_part *part,
376
             pool_t pool, const struct imap_arg *args,
377
             const char **error_r)
378
15.0k
{
379
15.0k
  struct message_part_data *data = part->data;
380
15.0k
  const struct imap_arg *list_args;
381
382
15.0k
  if (args->type == IMAP_ARG_EOL)
383
1.22k
    return 0;
384
13.8k
  if (args->type == IMAP_ARG_NIL)
385
12.7k
    args++;
386
1.07k
  else if (!imap_arg_get_list(args, &list_args)) {
387
4
    *error_r = "Invalid content-disposition list";
388
4
    return -1;
389
1.07k
  } else {
390
1.07k
    if (!imap_arg_get_string
391
1.07k
      (list_args++, &data->content_disposition)) {
392
2
      *error_r = "Invalid content-disposition";
393
2
      return -1;
394
2
    }
395
1.07k
    data->content_disposition = p_strdup(pool, data->content_disposition);
396
1.07k
    if (imap_bodystructure_params_parse(list_args, pool,
397
1.07k
      &data->content_disposition_params,
398
1.07k
      &data->content_disposition_params_count) < 0) {
399
7
      *error_r = "Invalid content-disposition params";
400
7
      return -1;
401
7
    }
402
1.06k
    args++;
403
1.06k
  }
404
13.8k
  if (args->type == IMAP_ARG_EOL)
405
680
    return 0;
406
13.1k
  if (imap_bodystructure_strlist_parse
407
13.1k
    (args++, pool, &data->content_language) < 0) {
408
20
    *error_r = "Invalid content-language";
409
20
    return -1;
410
20
  }
411
13.1k
  if (args->type == IMAP_ARG_EOL)
412
549
    return 0;
413
12.5k
  if (!imap_arg_get_nstring
414
12.5k
    (args++, &data->content_location)) {
415
2
    *error_r = "Invalid content-location";
416
2
    return -1;
417
2
  }
418
12.5k
  data->content_location = p_strdup(pool, data->content_location);
419
12.5k
  return 0;
420
12.5k
}
421
422
static int
423
imap_bodystructure_parse_args_int(
424
  unsigned int nesting, const struct imap_arg *args, pool_t pool,
425
  struct message_part **_part, const char **error_r)
426
29.2k
{
427
29.2k
  struct message_part *part = *_part, *child_part;;
428
29.2k
  struct message_part **child_part_p;
429
29.2k
  struct message_part_data *data;
430
29.2k
  const struct imap_arg *list_args;
431
29.2k
  const char *value, *content_type, *subtype, *error;
432
29.2k
  bool multipart, text, message_rfc822, parsing_tree, has_lines;
433
29.2k
  unsigned int lines;
434
29.2k
  uoff_t vsize;
435
436
29.2k
  ++nesting;
437
29.2k
  if (nesting > BODYSTRUCTURE_MAX_PARENTHESIS_NESTING) {
438
3
    *error_r = "Parts hierarchy nested too deep";
439
3
    return -1;
440
3
  }
441
29.2k
  if (part != NULL) {
442
    /* parsing with preexisting message_part tree */
443
0
    parsing_tree = FALSE;
444
29.2k
  } else {
445
    /* parsing message_part tree from BODYSTRUCTURE as well */
446
29.2k
    part = *_part = p_new(pool, struct message_part, 1);
447
29.2k
    parsing_tree = TRUE;
448
29.2k
  }
449
29.2k
  part->data = data = p_new(pool, struct message_part_data, 1);
450
451
29.2k
  multipart = FALSE;
452
29.2k
  if (!parsing_tree) {
453
0
    if ((part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0 &&
454
0
      part->children == NULL) {
455
0
      struct message_part_data dummy_part_data = {
456
0
        .content_type = "text",
457
0
        .content_subtype = "plain",
458
0
        .content_transfer_encoding = "7bit"
459
0
      };
460
0
      struct message_part dummy_part = {
461
0
        .parent = part,
462
0
        .data = &dummy_part_data,
463
0
        .flags = MESSAGE_PART_FLAG_TEXT
464
0
      };
465
0
      struct message_part *dummy_partp = &dummy_part;
466
467
      /* no parts in multipart message,
468
         that's not allowed. expect a single
469
         0-length text/plain structure */
470
0
      if (args->type != IMAP_ARG_LIST ||
471
0
        (args+1)->type == IMAP_ARG_LIST) {
472
0
        *error_r = "message_part hierarchy "
473
0
          "doesn't match BODYSTRUCTURE";
474
0
        return -1;
475
0
      }
476
477
0
      list_args = imap_arg_as_list(args);
478
0
      if (imap_bodystructure_parse_args_int(
479
0
        nesting, list_args, pool, &dummy_partp, error_r) < 0)
480
0
        return -1;
481
0
      child_part = NULL;
482
483
0
      multipart = TRUE;
484
0
      args++;
485
486
0
    } else {
487
0
      child_part = part->children;
488
0
      while (args->type == IMAP_ARG_LIST) {
489
0
        if ((part->flags & MESSAGE_PART_FLAG_MULTIPART) == 0 ||
490
0
            child_part == NULL) {
491
0
          *error_r = "message_part hierarchy "
492
0
            "doesn't match BODYSTRUCTURE";
493
0
          return -1;
494
0
        }
495
496
0
        list_args = imap_arg_as_list(args);
497
0
        if (imap_bodystructure_parse_args_int(
498
0
          nesting, list_args, pool, &child_part, error_r) < 0)
499
0
          return -1;
500
0
        child_part = child_part->next;
501
502
0
        multipart = TRUE;
503
0
        args++;
504
0
      }
505
0
    }
506
0
    if (multipart) {
507
0
      if (child_part != NULL) {
508
0
        *error_r = "message_part hierarchy "
509
0
          "doesn't match BODYSTRUCTURE";
510
0
        return -1;
511
0
      }
512
0
    } else if ((part->flags & MESSAGE_PART_FLAG_MULTIPART) != 0) {
513
0
      *error_r = "message_part multipart flag "
514
0
        "doesn't match BODYSTRUCTURE";
515
0
      return -1;
516
0
    }
517
29.2k
  } else {
518
29.2k
    child_part_p = &part->children;
519
48.2k
    while (args->type == IMAP_ARG_LIST) {
520
21.1k
      list_args = imap_arg_as_list(args);
521
21.1k
      if (imap_bodystructure_parse_args_int(
522
21.1k
        nesting, list_args, pool, child_part_p, error_r) < 0)
523
2.14k
        return -1;
524
19.0k
      (*child_part_p)->parent = part;
525
19.0k
      child_part_p = &(*child_part_p)->next;
526
527
19.0k
      multipart = TRUE;
528
19.0k
      args++;
529
19.0k
    }
530
27.0k
    if (multipart) {
531
9.79k
      part->flags |= MESSAGE_PART_FLAG_MULTIPART;
532
9.79k
    }
533
27.0k
  }
534
535
27.0k
  if (multipart) {
536
9.79k
    data->content_type = "multipart";
537
9.79k
    if (!imap_arg_get_string(args++, &data->content_subtype)) {
538
17
      *error_r = "Invalid multipart content-type";
539
17
      return -1;
540
17
    }
541
9.77k
    data->content_subtype = p_strdup(pool, data->content_subtype);
542
9.77k
    if (args->type == IMAP_ARG_EOL)
543
3.67k
      return 0;
544
6.10k
    if (imap_bodystructure_params_parse(args++, pool,
545
6.10k
      &data->content_type_params,
546
6.10k
      &data->content_type_params_count) < 0) {
547
2
      *error_r = "Invalid content params";
548
2
      return -1;
549
2
    }
550
6.10k
    return imap_bodystructure_parse_args_common
551
6.10k
      (part, pool, args, error_r);
552
6.10k
  }
553
554
  /* "content type" "subtype" */
555
17.2k
  if (!imap_arg_get_string(&args[0], &content_type) ||
556
17.0k
      !imap_arg_get_string(&args[1], &subtype)) {
557
371
    *error_r = "Invalid content-type";
558
371
    return -1;
559
371
  }
560
16.9k
  data->content_type = p_strdup(pool, content_type);
561
16.9k
  data->content_subtype = p_strdup(pool, subtype);
562
16.9k
  args += 2;
563
564
16.9k
  text = strcasecmp(content_type, "text") == 0;
565
16.9k
  message_rfc822 = strcasecmp(content_type, "message") == 0 &&
566
4.17k
    strcasecmp(subtype, "rfc822") == 0;
567
568
16.9k
  if (!parsing_tree) {
569
#if 0
570
    /* Disabled for now. Earlier Dovecot versions handled broken
571
       Content-Type headers by writing them as "text" "plain" to
572
       BODYSTRUCTURE reply, but the message_part didn't have
573
       MESSAGE_PART_FLAG_TEXT. */
574
    if (text != ((part->flags & MESSAGE_PART_FLAG_TEXT) != 0)) {
575
      *error_r = "message_part text flag "
576
        "doesn't match BODYSTRUCTURE";
577
      return -1;
578
    }
579
#endif
580
0
    if (message_rfc822 !=
581
0
      ((part->flags & MESSAGE_PART_FLAG_MESSAGE_RFC822) != 0)) {
582
0
      *error_r = "message_part message/rfc822 flag "
583
0
        "doesn't match BODYSTRUCTURE";
584
0
      return -1;
585
0
    }
586
16.9k
  } else {
587
16.9k
    if (text)
588
2.23k
      part->flags |= MESSAGE_PART_FLAG_TEXT;
589
16.9k
    if (message_rfc822)
590
3.05k
      part->flags |= MESSAGE_PART_FLAG_MESSAGE_RFC822;
591
16.9k
  }
592
593
  /* ("content type param key" "value" ...) | NIL */
594
16.9k
  if (imap_bodystructure_params_parse(args++, pool,
595
16.9k
    &data->content_type_params,
596
16.9k
    &data->content_type_params_count) < 0) {
597
357
    *error_r = "Invalid content params";
598
357
    return -1;
599
357
  }
600
601
  /* "content id" "content description" "transfer encoding" size */
602
16.5k
  if (!imap_arg_get_nstring(args++, &data->content_id)) {
603
12
    *error_r = "Invalid content-id";
604
12
    return -1;
605
12
  }
606
16.5k
  if (!imap_arg_get_nstring(args++, &data->content_description)) {
607
13
    *error_r = "Invalid content-description";
608
13
    return -1;
609
13
  }
610
16.5k
  if (!imap_arg_get_string(args++, &data->content_transfer_encoding)) {
611
13
    *error_r = "Invalid content-transfer-encoding";
612
13
    return -1;
613
13
  }
614
16.5k
  data->content_id = p_strdup(pool, data->content_id);
615
16.5k
  data->content_description = p_strdup(pool, data->content_description);
616
16.5k
  data->content_transfer_encoding =
617
16.5k
    p_strdup(pool, data->content_transfer_encoding);
618
16.5k
  if (!imap_arg_get_atom(args++, &value) ||
619
16.5k
      str_to_uoff(value, &vsize) < 0) {
620
141
    *error_r = "Invalid size field";
621
141
    return -1;
622
141
  }
623
16.3k
  if (!parsing_tree) {
624
0
    if (vsize != part->body_size.virtual_size) {
625
0
      *error_r = "message_part virtual_size doesn't match "
626
0
        "size in BODYSTRUCTURE";
627
0
      return -1;
628
0
    }
629
16.3k
  } else {
630
16.3k
    part->body_size.virtual_size = vsize;
631
16.3k
  }
632
633
16.3k
  if (text) {
634
    /* text/xxx - text lines */
635
2.22k
    if (!imap_arg_get_atom(args++, &value) ||
636
2.22k
        str_to_uint(value, &lines) < 0) {
637
214
      *error_r = "Invalid lines field";
638
214
      return -1;
639
214
    }
640
2.22k
    i_assert(part->children == NULL);
641
2.01k
    has_lines = TRUE;
642
14.1k
  } else if (message_rfc822) {
643
    /* message/rfc822 - envelope + bodystructure + text lines */
644
645
3.05k
    if (!parsing_tree) {
646
0
      i_assert(part->children != NULL &&
647
0
         part->children->next == NULL);
648
0
    }
649
650
3.05k
    if (!imap_arg_get_list(&args[1], &list_args)) {
651
3
      *error_r = "Child bodystructure list expected";
652
3
      return -1;
653
3
    }
654
3.04k
    if (imap_bodystructure_parse_args_int(
655
3.04k
      nesting, list_args, pool, &part->children, error_r) < 0)
656
78
      return -1;
657
2.97k
    if (parsing_tree) {
658
2.97k
      i_assert(part->children != NULL &&
659
2.97k
         part->children->next == NULL);
660
2.97k
      part->children->parent = part;
661
2.97k
    }
662
663
2.97k
    if (!imap_arg_get_list(&args[0], &list_args)) {
664
1
      *error_r = "Envelope list expected";
665
1
      return -1;
666
1
    }
667
2.96k
    if (!imap_envelope_parse_args(list_args, pool,
668
2.96k
      &part->children->data->envelope, &error)) {
669
54
      *error_r = t_strdup_printf
670
54
        ("Invalid envelope list: %s", error);
671
54
      return -1;
672
54
    }
673
2.91k
    args += 2;
674
2.91k
    if (!imap_arg_get_atom(args++, &value) ||
675
2.91k
        str_to_uint(value, &lines) < 0) {
676
5
      *error_r = "Invalid lines field";
677
5
      return -1;
678
5
    }
679
2.91k
    has_lines = TRUE;
680
11.0k
  } else {
681
11.0k
    i_assert(part->children == NULL);
682
11.0k
    lines = 0;
683
11.0k
    has_lines = FALSE;
684
11.0k
  }
685
16.0k
  if (!parsing_tree) {
686
0
    if (has_lines && lines != part->body_size.lines) {
687
0
      *error_r = "message_part lines "
688
0
        "doesn't match lines in BODYSTRUCTURE";
689
0
      return -1;
690
0
    }
691
16.0k
  } else {
692
16.0k
    part->body_size.lines = lines;
693
16.0k
  }
694
16.0k
  if (args->type == IMAP_ARG_EOL)
695
7.06k
    return 0;
696
8.95k
  if (!imap_arg_get_nstring(args++, &data->content_md5)) {
697
6
    *error_r = "Invalid content-md5";
698
6
    return -1;
699
6
  }
700
8.95k
  data->content_md5 = p_strdup(pool, data->content_md5);
701
8.95k
  return imap_bodystructure_parse_args_common
702
8.95k
    (part, pool, args, error_r);
703
8.95k
}
704
705
int
706
imap_bodystructure_parse_args(const struct imap_arg *args, pool_t pool,
707
            struct message_part **_part,
708
            const char **error_r)
709
4.99k
{
710
4.99k
  return imap_bodystructure_parse_args_int(0, args, pool, _part, error_r);
711
4.99k
}
712
713
static void imap_bodystructure_reset_data(struct message_part *parts)
714
6.58k
{
715
11.0k
  for (; parts != NULL; parts = parts->next) {
716
4.50k
    parts->data = NULL;
717
4.50k
    imap_bodystructure_reset_data(parts->children);
718
4.50k
  }
719
6.58k
}
720
721
int imap_bodystructure_parse_full(const char *bodystructure,
722
  pool_t pool, struct message_part **parts,
723
  const char **error_r)
724
5.81k
{
725
5.81k
  struct istream *input;
726
5.81k
  struct imap_parser *parser;
727
5.81k
  const struct imap_arg *args;
728
5.81k
  int ret;
729
730
5.81k
  i_assert(*parts == NULL || (*parts)->next == NULL);
731
732
5.81k
  input = i_stream_create_from_data(bodystructure, strlen(bodystructure));
733
5.81k
  (void)i_stream_read(input);
734
735
5.81k
  parser = imap_parser_create(input, NULL, SIZE_MAX, NULL);
736
5.81k
  ret = imap_parser_finish_line(parser, 0,
737
5.81k
              IMAP_PARSE_FLAG_LITERAL_TYPE, &args);
738
5.81k
  if (ret < 0) {
739
426
    *error_r = t_strdup_printf("IMAP parser failed: %s",
740
426
             imap_parser_get_error(parser, NULL));
741
5.39k
  } else if (ret == 0) {
742
401
    *error_r = "Empty bodystructure";
743
401
    ret = -1;
744
4.99k
  } else {
745
4.99k
    T_BEGIN {
746
4.99k
      ret = imap_bodystructure_parse_args
747
4.99k
        (args, pool, parts, error_r);
748
4.99k
    } T_END_PASS_STR_IF(ret < 0, error_r);
749
4.99k
  }
750
751
5.81k
  if (ret < 0) {
752
    /* Don't leave partially filled data to message_parts. Some of
753
       the code expects that if the first message_part->data is
754
       filled, they all are. */
755
2.07k
    imap_bodystructure_reset_data(*parts);
756
2.07k
  }
757
758
5.81k
  imap_parser_unref(&parser);
759
5.81k
  i_stream_destroy(&input);
760
5.81k
  return ret;
761
5.81k
}
762
763
int imap_bodystructure_parse(const char *bodystructure,
764
  pool_t pool, struct message_part *parts,
765
  const char **error_r)
766
0
{
767
0
  i_assert(parts != NULL);
768
769
0
  return imap_bodystructure_parse_full(bodystructure,
770
0
    pool, &parts, error_r);
771
0
}
772
773
/*
774
 * IMAP BODYSTRUCTURE to BODY conversion
775
 */
776
777
static bool str_append_nstring(string_t *str, const struct imap_arg *arg)
778
0
{
779
0
  const char *cstr;
780
781
0
  if (!imap_arg_get_nstring(arg, &cstr))
782
0
    return FALSE;
783
784
0
  switch (arg->type) {
785
0
  case IMAP_ARG_NIL:
786
0
    str_append(str, "NIL");
787
0
    break;
788
0
  case IMAP_ARG_STRING:
789
0
    str_append_c(str, '"');
790
    /* NOTE: we're parsing with no-unescape flag,
791
       so don't double-escape it here */
792
0
    str_append(str, cstr);
793
0
    str_append_c(str, '"');
794
0
    break;
795
0
  case IMAP_ARG_LITERAL: {
796
0
    str_printfa(str, "{%zu}\r\n", strlen(cstr));
797
0
    str_append(str, cstr);
798
0
    break;
799
0
  }
800
0
  default:
801
0
    i_unreached();
802
0
    return FALSE;
803
0
  }
804
0
  return TRUE;
805
0
}
806
807
static void
808
imap_write_envelope_list(const struct imap_arg *args, string_t *str,
809
  bool toplevel)
810
0
{
811
0
  const struct imap_arg *children;
812
813
  /* don't do any typechecking, just write it out */
814
0
  while (!IMAP_ARG_IS_EOL(args)) {
815
0
    bool list = FALSE;
816
817
0
    if (!str_append_nstring(str, args)) {
818
0
      if (!imap_arg_get_list(args, &children)) {
819
        /* everything is either nstring or list */
820
0
        i_unreached();
821
0
      }
822
823
0
      str_append_c(str, '(');
824
0
      imap_write_envelope_list(children, str, FALSE);
825
0
      str_append_c(str, ')');
826
827
0
      list = TRUE;
828
0
    }
829
0
    args++;
830
831
0
    if ((toplevel || !list) && !IMAP_ARG_IS_EOL(args))
832
0
      str_append_c(str, ' ');
833
0
  }
834
0
}
835
836
static void
837
imap_write_envelope(const struct imap_arg *args, string_t *str)
838
0
{
839
0
  imap_write_envelope_list(args, str, TRUE);
840
0
}
841
842
static int imap_parse_bodystructure_args(const struct imap_arg *args,
843
           string_t *str, const char **error_r)
844
0
{
845
0
  const struct imap_arg *subargs;
846
0
  const struct imap_arg *list_args;
847
0
  const char *value, *content_type, *subtype;
848
0
  bool multipart, text, message_rfc822;
849
0
  int i;
850
851
0
  multipart = FALSE;
852
0
  while (args->type == IMAP_ARG_LIST) {
853
0
    str_append_c(str, '(');
854
0
    list_args = imap_arg_as_list(args);
855
0
    if (imap_parse_bodystructure_args(list_args, str, error_r) < 0)
856
0
      return -1;
857
0
    str_append_c(str, ')');
858
859
0
    multipart = TRUE;
860
0
    args++;
861
0
  }
862
863
0
  if (multipart) {
864
    /* next is subtype of Content-Type. rest is skipped. */
865
0
    str_append_c(str, ' ');
866
0
    if (!str_append_nstring(str, args)) {
867
0
      *error_r = "Invalid multipart content-type";
868
0
      return -1;
869
0
    }
870
0
    return 0;
871
0
  }
872
873
  /* "content type" "subtype" */
874
0
  if (!imap_arg_get_string(&args[0], &content_type) ||
875
0
      !imap_arg_get_string(&args[1], &subtype)) {
876
0
    *error_r = "Invalid content-type";
877
0
    return -1;
878
0
  }
879
880
0
  if (!str_append_nstring(str, &args[0]))
881
0
    i_unreached();
882
0
  str_append_c(str, ' ');
883
0
  if (!str_append_nstring(str, &args[1]))
884
0
    i_unreached();
885
886
0
  text = strcasecmp(content_type, "text") == 0;
887
0
  message_rfc822 = strcasecmp(content_type, "message") == 0 &&
888
0
    strcasecmp(subtype, "rfc822") == 0;
889
890
0
  args += 2;
891
892
  /* ("content type param key" "value" ...) | NIL */
893
0
  if (imap_arg_get_list(args, &subargs)) {
894
0
    str_append(str, " (");
895
0
    while (!IMAP_ARG_IS_EOL(subargs)) {
896
0
      if (!str_append_nstring(str, &subargs[0])) {
897
0
        *error_r = "Invalid content param key";
898
0
        return -1;
899
0
      }
900
0
      str_append_c(str, ' ');
901
0
      if (!str_append_nstring(str, &subargs[1])) {
902
0
        *error_r = "Invalid content param value";
903
0
        return -1;
904
0
      }
905
906
0
      subargs += 2;
907
0
      if (IMAP_ARG_IS_EOL(subargs))
908
0
        break;
909
0
      str_append_c(str, ' ');
910
0
    }
911
0
    str_append(str, ")");
912
0
  } else if (args->type == IMAP_ARG_NIL) {
913
0
    str_append(str, " NIL");
914
0
  } else {
915
0
    *error_r = "list/NIL expected";
916
0
    return -1;
917
0
  }
918
0
  args++;
919
920
  /* "content id" "content description" "transfer encoding" size */
921
0
  for (i = 0; i < 3; i++, args++) {
922
0
    str_append_c(str, ' ');
923
924
0
    if (!str_append_nstring(str, args)) {
925
0
      *error_r = "nstring expected";
926
0
      return -1;
927
0
    }
928
0
  }
929
0
  if (!imap_arg_get_atom(args, &value)) {
930
0
    *error_r = "atom expected for size";
931
0
    return -1;
932
0
  }
933
0
  str_printfa(str, " %s", value);
934
0
  args++;
935
936
0
  if (text) {
937
    /* text/xxx - text lines */
938
0
    if (!imap_arg_get_atom(args, &value)) {
939
0
      *error_r = "Text lines expected";
940
0
      return -1;
941
0
    }
942
943
0
    str_append_c(str, ' ');
944
0
    str_append(str, value);
945
0
  } else if (message_rfc822) {
946
    /* message/rfc822 - envelope + bodystructure + text lines */
947
0
    str_append_c(str, ' ');
948
949
0
    if (!imap_arg_get_list(&args[0], &list_args)) {
950
0
      *error_r = "Envelope list expected";
951
0
      return -1;
952
0
    }
953
0
    str_append_c(str, '(');
954
0
    imap_write_envelope(list_args, str);
955
0
    str_append(str, ") (");
956
957
0
    if (!imap_arg_get_list(&args[1], &list_args)) {
958
0
      *error_r = "Child bodystructure list expected";
959
0
      return -1;
960
0
    }
961
0
    if (imap_parse_bodystructure_args(list_args, str, error_r) < 0)
962
0
      return -1;
963
964
0
    str_append(str, ") ");
965
0
    if (!imap_arg_get_atom(&args[2], &value)) {
966
0
      *error_r = "Text lines expected";
967
0
      return -1;
968
0
    }
969
0
    str_append(str, value);
970
0
  }
971
0
  return 0;
972
0
}
973
974
int imap_body_parse_from_bodystructure(const char *bodystructure,
975
               string_t *dest, const char **error_r)
976
0
{
977
0
  struct istream *input;
978
0
  struct imap_parser *parser;
979
0
  const struct imap_arg *args;
980
0
  int ret;
981
982
0
  input = i_stream_create_from_data(bodystructure, strlen(bodystructure));
983
0
  (void)i_stream_read(input);
984
985
0
  parser = imap_parser_create(input, NULL, SIZE_MAX, NULL);
986
0
  ret = imap_parser_finish_line(parser, 0, IMAP_PARSE_FLAG_NO_UNESCAPE |
987
0
              IMAP_PARSE_FLAG_LITERAL_TYPE, &args);
988
0
  if (ret < 0) {
989
0
    *error_r = t_strdup_printf("IMAP parser failed: %s",
990
0
             imap_parser_get_error(parser, NULL));
991
0
  } else if (ret == 0) {
992
0
    *error_r = "Empty bodystructure";
993
0
    ret = -1;
994
0
  } else {
995
0
    ret = imap_parse_bodystructure_args(args, dest, error_r);
996
0
  }
997
998
0
  imap_parser_unref(&parser);
999
0
  i_stream_destroy(&input);
1000
0
  return ret;
1001
0
}