Coverage Report

Created: 2025-12-11 07:13

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