Coverage Report

Created: 2026-05-30 06:37

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib-storage/index/index-mail-binary.c
Line
Count
Source
1
/* Copyright (c) 2002-2018 Dovecot authors, see the included COPYING file */
2
3
#include "lib.h"
4
#include "str.h"
5
#include "safe-mkstemp.h"
6
#include "istream.h"
7
#include "istream-crlf.h"
8
#include "istream-seekable.h"
9
#include "istream-base64.h"
10
#include "istream-qp.h"
11
#include "istream-header-filter.h"
12
#include "ostream.h"
13
#include "message-binary-part.h"
14
#include "message-parser.h"
15
#include "message-decoder.h"
16
#include "mail-user.h"
17
#include "index-storage.h"
18
#include "index-mail.h"
19
20
#define MAIL_BINARY_CACHE_EXPIRE_MSECS (60*1000)
21
22
#define IS_CONVERTED_CTE(cte) \
23
0
  ((cte) == MESSAGE_CTE_QP || (cte) == MESSAGE_CTE_BASE64)
24
25
struct binary_block {
26
  struct istream *input;
27
  uoff_t physical_pos;
28
  unsigned int body_lines_count;
29
  bool converted, converted_hdr;
30
};
31
32
struct binary_ctx {
33
  struct mail *mail;
34
  struct istream *input;
35
  bool has_nuls, converted;
36
  /* each block is its own input stream. basically each converted MIME
37
     body has its own block and the parts between the MIME bodies are
38
     unconverted blocks */
39
  ARRAY(struct binary_block) blocks;
40
41
  uoff_t copy_start_offset;
42
};
43
44
static void binary_copy_to(struct binary_ctx *ctx, uoff_t end_offset)
45
0
{
46
0
  struct binary_block *block;
47
0
  struct istream *linput, *cinput;
48
0
  uoff_t orig_offset, size;
49
50
0
  i_assert(end_offset >= ctx->copy_start_offset);
51
52
0
  if (end_offset == ctx->copy_start_offset)
53
0
    return;
54
55
0
  size = end_offset - ctx->copy_start_offset;
56
0
  orig_offset = ctx->input->v_offset;
57
58
0
  i_stream_seek(ctx->input, ctx->copy_start_offset);
59
0
  linput = i_stream_create_limit(ctx->input, size);
60
0
  cinput = i_stream_create_crlf(linput);
61
0
  i_stream_unref(&linput);
62
63
0
  block = array_append_space(&ctx->blocks);
64
0
  block->input = cinput;
65
66
0
  i_stream_seek(ctx->input, orig_offset);
67
0
}
68
69
static void
70
binary_cte_filter_callback(struct header_filter_istream *input,
71
         struct message_header_line *hdr,
72
         bool *matched ATTR_UNUSED, void *context ATTR_UNUSED)
73
0
{
74
0
  static const char *cte_binary = "Content-Transfer-Encoding: binary\r\n";
75
76
0
  if (hdr != NULL && hdr->eoh) {
77
0
    i_stream_header_filter_add(input, cte_binary,
78
0
             strlen(cte_binary));
79
0
  }
80
0
}
81
82
static int
83
add_binary_part(struct binary_ctx *ctx, const struct message_part *part,
84
    bool include_hdr)
85
0
{
86
0
  static const char *filter_headers[] = {
87
0
    "Content-Transfer-Encoding",
88
0
  };
89
0
  struct message_header_parser_ctx *parser;
90
0
  struct message_header_line *hdr;
91
0
  struct message_part *child;
92
0
  struct message_size hdr_size;
93
0
  struct istream *linput;
94
0
  struct binary_block *block;
95
0
  enum message_cte cte;
96
0
  uoff_t part_end_offset;
97
0
  int ret;
98
99
  /* first parse the header to find c-t-e. */
100
0
  i_stream_seek(ctx->input, part->physical_pos);
101
102
0
  cte = MESSAGE_CTE_78BIT;
103
0
  parser = message_parse_header_init(ctx->input, &hdr_size, 0);
104
0
  while ((ret = message_parse_header_next(parser, &hdr)) > 0) {
105
0
    if (strcasecmp(hdr->name, "Content-Transfer-Encoding") == 0)
106
0
      cte = message_decoder_parse_cte(hdr);
107
0
  }
108
0
  i_assert(ret < 0);
109
0
  if (message_parse_header_has_nuls(parser)) {
110
    /* we're not converting NULs to 0x80 when doing a binary fetch,
111
       even if they're in the message header. */
112
0
    ctx->has_nuls = TRUE;
113
0
  }
114
0
  message_parse_header_deinit(&parser);
115
116
0
  if (ctx->input->stream_errno != 0) {
117
0
    mail_set_critical(ctx->mail,
118
0
      "read(%s) failed: %s", i_stream_get_name(ctx->input),
119
0
      i_stream_get_error(ctx->input));
120
0
    return -1;
121
0
  }
122
123
0
  if (cte == MESSAGE_CTE_UNKNOWN) {
124
0
    mail_storage_set_error(ctx->mail->box->storage,
125
0
               MAIL_ERROR_CONVERSION,
126
0
               "Unknown Content-Transfer-Encoding");
127
0
    return -1;
128
0
  }
129
130
0
  i_stream_seek(ctx->input, part->physical_pos);
131
0
  if (!include_hdr) {
132
    /* body only */
133
0
  } else if (IS_CONVERTED_CTE(cte)) {
134
    /* write header with modified content-type */
135
0
    if (ctx->copy_start_offset != 0)
136
0
      binary_copy_to(ctx, part->physical_pos);
137
0
    block = array_append_space(&ctx->blocks);
138
0
    block->physical_pos = part->physical_pos;
139
0
    block->converted = TRUE;
140
0
    block->converted_hdr = TRUE;
141
142
0
    linput = i_stream_create_limit(ctx->input, UOFF_T_MAX);
143
0
    block->input = i_stream_create_header_filter(linput,
144
0
        HEADER_FILTER_EXCLUDE | HEADER_FILTER_HIDE_BODY,
145
0
        filter_headers, N_ELEMENTS(filter_headers),
146
0
        binary_cte_filter_callback, NULL);
147
0
    i_stream_unref(&linput);
148
0
  } else {
149
    /* copy everything as-is until the end of this header */
150
0
    binary_copy_to(ctx, part->physical_pos +
151
0
             part->header_size.physical_size);
152
0
  }
153
0
  ctx->copy_start_offset = part->physical_pos +
154
0
    part->header_size.physical_size;
155
0
  part_end_offset = part->physical_pos +
156
0
    part->header_size.physical_size +
157
0
    part->body_size.physical_size;
158
159
0
  if (part->children != NULL) {
160
    /* multipart */
161
0
    for (child = part->children; child != NULL; child = child->next) {
162
0
      if (add_binary_part(ctx, child, TRUE) < 0)
163
0
        return -1;
164
0
    }
165
0
    binary_copy_to(ctx, part_end_offset);
166
0
    ctx->copy_start_offset = part_end_offset;
167
0
    return 0;
168
0
  }
169
0
  if (part->body_size.physical_size == 0) {
170
    /* no body */
171
0
    ctx->copy_start_offset = part_end_offset;
172
0
    return 0;
173
0
  }
174
175
  /* single part - write decoded data */
176
0
  block = array_append_space(&ctx->blocks);
177
0
  block->physical_pos = part->physical_pos;
178
179
0
  i_stream_seek(ctx->input, part->physical_pos +
180
0
          part->header_size.physical_size);
181
0
  linput = i_stream_create_limit(ctx->input, part->body_size.physical_size);
182
0
  switch (cte) {
183
0
  case MESSAGE_CTE_UNKNOWN:
184
0
    i_unreached();
185
0
  case MESSAGE_CTE_78BIT:
186
0
  case MESSAGE_CTE_BINARY:
187
    /* no conversion necessary */
188
0
    if ((part->flags & MESSAGE_PART_FLAG_HAS_NULS) != 0)
189
0
      ctx->has_nuls = TRUE;
190
0
    block->input = i_stream_create_crlf(linput);
191
0
    break;
192
0
  case MESSAGE_CTE_QP:
193
0
    block->input = i_stream_create_qp_decoder(linput);
194
0
    ctx->converted = block->converted = TRUE;
195
0
    break;
196
0
  case MESSAGE_CTE_BASE64:
197
0
    block->input = i_stream_create_base64_decoder(linput);
198
0
    ctx->converted = block->converted = TRUE;
199
0
    break;
200
0
  }
201
0
  i_stream_unref(&linput);
202
203
0
  ctx->copy_start_offset = part_end_offset;
204
0
  return 0;
205
0
}
206
207
static int fd_callback(const char **path_r, void *context)
208
0
{
209
0
  struct mail *_mail = context;
210
0
  string_t *path;
211
0
  int fd;
212
213
0
  path = t_str_new(256);
214
0
  mail_user_set_get_temp_prefix(path, _mail->box->storage->user->set);
215
0
  fd = safe_mkstemp_hostpid(path, 0600, (uid_t)-1, (gid_t)-1);
216
0
  if (fd == -1) {
217
0
    e_error(mail_event(_mail),
218
0
      "Temp file creation to %s failed: %m", str_c(path));
219
0
    return -1;
220
0
  }
221
222
  /* we just want the fd, unlink it */
223
0
  if (i_unlink(str_c(path)) < 0) {
224
    /* shouldn't happen.. */
225
0
    i_close_fd(&fd);
226
0
    return -1;
227
0
  }
228
0
  *path_r = str_c(path);
229
0
  return fd;
230
0
}
231
232
static void binary_streams_free(struct binary_ctx *ctx)
233
0
{
234
0
  struct binary_block *block;
235
236
0
  array_foreach_modifiable(&ctx->blocks, block)
237
0
    i_stream_unref(&block->input);
238
0
}
239
240
static void
241
binary_parts_update(struct binary_ctx *ctx, const struct message_part *part,
242
        struct message_binary_part **msg_bin_parts)
243
0
{
244
0
  struct index_mail *mail = INDEX_MAIL(ctx->mail);
245
0
  struct binary_block *blocks;
246
0
  struct message_binary_part bin_part;
247
0
  unsigned int i, count;
248
0
  uoff_t size;
249
0
  bool found;
250
251
0
  blocks = array_get_modifiable(&ctx->blocks, &count);
252
0
  for (; part != NULL; part = part->next) {
253
0
    binary_parts_update(ctx, part->children, msg_bin_parts);
254
255
0
    i_zero(&bin_part);
256
    /* default to unchanged header */
257
0
    bin_part.binary_hdr_size = part->header_size.virtual_size;
258
0
    bin_part.physical_pos = part->physical_pos;
259
0
    found = FALSE;
260
0
    for (i = 0; i < count; i++) {
261
0
      if (blocks[i].physical_pos != part->physical_pos ||
262
0
          !blocks[i].converted)
263
0
        continue;
264
265
0
      size = blocks[i].input->v_offset;
266
0
      if (blocks[i].converted_hdr)
267
0
        bin_part.binary_hdr_size = size;
268
0
      else
269
0
        bin_part.binary_body_size = size;
270
0
      found = TRUE;
271
0
    }
272
0
    if (found) {
273
0
      bin_part.next = *msg_bin_parts;
274
0
      *msg_bin_parts = p_new(mail->mail.data_pool,
275
0
                 struct message_binary_part, 1);
276
0
      **msg_bin_parts = bin_part;
277
0
    }
278
0
  }
279
0
}
280
281
static void binary_parts_cache(struct binary_ctx *ctx)
282
0
{
283
0
  struct index_mail *mail = INDEX_MAIL(ctx->mail);
284
0
  buffer_t *buf;
285
286
0
  buf = t_buffer_create(128);
287
0
  message_binary_part_serialize(mail->data.bin_parts, buf);
288
0
  index_mail_cache_add(mail, MAIL_CACHE_BINARY_PARTS,
289
0
           buf->data, buf->used);
290
0
}
291
292
static struct istream **blocks_get_streams(struct binary_ctx *ctx)
293
0
{
294
0
  struct istream **streams;
295
0
  const struct binary_block *blocks;
296
0
  unsigned int i, count;
297
298
0
  blocks = array_get(&ctx->blocks, &count);
299
0
  streams = t_new(struct istream *, count+1);
300
0
  for (i = 0; i < count; i++) {
301
0
    streams[i] = blocks[i].input;
302
0
    i_assert(streams[i]->v_offset == 0);
303
0
  }
304
0
  return streams;
305
0
}
306
307
static int
308
blocks_count_lines(struct binary_ctx *ctx, struct istream *full_input)
309
0
{
310
0
  struct binary_block *blocks, *cur_block;
311
0
  unsigned int block_idx, block_count;
312
0
  uoff_t cur_block_offset, cur_block_size;
313
0
  const unsigned char *data, *p;
314
0
  size_t size, skip;
315
0
  ssize_t ret;
316
317
0
  blocks = array_get_modifiable(&ctx->blocks, &block_count);
318
0
  cur_block = blocks;
319
0
  cur_block_offset = 0;
320
0
  block_idx = 0;
321
322
  /* count the number of lines each block contains */
323
0
  while ((ret = i_stream_read_more(full_input, &data, &size)) > 0) {
324
0
    i_assert(cur_block_offset <= cur_block->input->v_offset);
325
0
    if (cur_block->input->eof) {
326
      /* this is the last input for this block. the input
327
         may also contain the next block's data, which we
328
         don't want to include in this block's line count. */
329
0
      cur_block_size = cur_block->input->v_offset +
330
0
        i_stream_get_data_size(cur_block->input);
331
0
      i_assert(size >= cur_block_size - cur_block_offset);
332
0
      size = cur_block_size - cur_block_offset;
333
0
    }
334
0
    skip = size;
335
0
    while ((p = memchr(data, '\n', size)) != NULL) {
336
0
      size -= p-data+1;
337
0
      data = p+1;
338
0
      cur_block->body_lines_count++;
339
0
    }
340
0
    i_stream_skip(full_input, skip);
341
0
    cur_block_offset += skip;
342
343
0
    if (i_stream_read_eof(cur_block->input)) {
344
      /* go to the next block */
345
0
      if (block_idx+1 == block_count) {
346
0
        i_assert(i_stream_read_eof(full_input));
347
0
        ret = -1;
348
0
        break;
349
0
      }
350
0
      block_idx++;
351
0
      cur_block++;
352
0
      cur_block_offset = 0;
353
0
    }
354
0
  }
355
0
  i_assert(ret == -1);
356
0
  if (full_input->stream_errno != 0)
357
0
    return -1;
358
0
  i_assert(block_count == 0 || !i_stream_have_bytes_left(cur_block->input));
359
0
  i_assert(block_count == 0 || block_idx+1 == block_count);
360
0
  return 0;
361
0
}
362
363
static int
364
index_mail_read_binary_to_cache(struct mail *_mail,
365
        const struct message_part *part,
366
        bool include_hdr, const char *reason,
367
        bool *binary_r, bool *converted_r)
368
0
{
369
0
  struct index_mail *mail = INDEX_MAIL(_mail);
370
0
  struct mail_binary_cache *cache = &_mail->box->storage->binary_cache;
371
0
  struct binary_ctx ctx;
372
0
  struct istream *is;
373
374
0
  i_zero(&ctx);
375
0
  ctx.mail = _mail;
376
0
  t_array_init(&ctx.blocks, 8);
377
378
0
  mail_storage_free_binary_cache(_mail->box->storage);
379
0
  if (mail_get_stream_because(_mail, NULL, NULL, reason, &ctx.input) < 0)
380
0
    return -1;
381
382
0
  if (add_binary_part(&ctx, part, include_hdr) < 0) {
383
0
    binary_streams_free(&ctx);
384
0
    return -1;
385
0
  }
386
387
0
  if (array_count(&ctx.blocks) != 0) {
388
0
    is = i_streams_merge(blocks_get_streams(&ctx),
389
0
                 IO_BLOCK_SIZE,
390
0
                 fd_callback, _mail);
391
0
  } else {
392
0
    is = i_stream_create_from_data("", 0);
393
0
  }
394
0
  i_stream_set_name(is, t_strdup_printf(
395
0
    "<binary stream of mailbox %s UID %u>",
396
0
    _mail->box->vname, _mail->uid));
397
0
  if (blocks_count_lines(&ctx, is) < 0) {
398
0
    if (is->stream_errno == EINVAL) {
399
      /* MIME part contains invalid data */
400
0
      mail_storage_set_error(_mail->box->storage,
401
0
                 MAIL_ERROR_INVALIDDATA,
402
0
                 "Invalid data in MIME part");
403
0
    } else {
404
0
      mail_set_critical(_mail, "read(%s) failed: %s",
405
0
            i_stream_get_name(is),
406
0
            i_stream_get_error(is));
407
0
    }
408
0
    i_stream_unref(&is);
409
0
    binary_streams_free(&ctx);
410
0
    return -1;
411
0
  }
412
413
0
  if (_mail->uid > 0) {
414
0
    cache->to = timeout_add(MAIL_BINARY_CACHE_EXPIRE_MSECS,
415
0
          mail_storage_free_binary_cache,
416
0
          _mail->box->storage);
417
0
    cache->box = _mail->box;
418
0
    cache->uid = _mail->uid;
419
0
    cache->orig_physical_pos = part->physical_pos;
420
0
    cache->include_hdr = include_hdr;
421
0
    cache->input = is;
422
0
  }
423
424
0
  i_assert(!i_stream_have_bytes_left(is));
425
0
  cache->size = is->v_offset;
426
0
  i_stream_seek(is, 0);
427
428
0
  if (part->parent == NULL && include_hdr &&
429
0
      mail->data.bin_parts == NULL) {
430
0
    binary_parts_update(&ctx, part, &mail->data.bin_parts);
431
0
    if (_mail->uid > 0)
432
0
      binary_parts_cache(&ctx);
433
0
  }
434
0
  binary_streams_free(&ctx);
435
436
0
  *binary_r = ctx.converted ? TRUE : ctx.has_nuls;
437
0
  *converted_r = ctx.converted;
438
0
  return 0;
439
0
}
440
441
static bool get_cached_binary_parts(struct index_mail *mail)
442
0
{
443
0
  const unsigned int field_idx =
444
0
    mail->ibox->cache_fields[MAIL_CACHE_BINARY_PARTS].idx;
445
0
  buffer_t *part_buf;
446
0
  int ret;
447
448
0
  if (mail->data.bin_parts != NULL)
449
0
    return TRUE;
450
451
0
  part_buf = t_buffer_create(128);
452
0
  ret = index_mail_cache_lookup_field(mail, part_buf, field_idx);
453
0
  if (ret <= 0)
454
0
    return FALSE;
455
456
0
  if (message_binary_part_deserialize(mail->mail.data_pool,
457
0
              part_buf->data, part_buf->used,
458
0
              &mail->data.bin_parts) < 0) {
459
0
    mail_set_mail_cache_corrupted(&mail->mail.mail,
460
0
      "Corrupted cached binary.parts data");
461
0
    return FALSE;
462
0
  }
463
0
  return TRUE;
464
0
}
465
466
static struct message_part *
467
msg_part_find(struct message_part *parts, uoff_t physical_pos)
468
0
{
469
0
  struct message_part *part, *child;
470
471
0
  for (part = parts; part != NULL; part = part->next) {
472
0
    if (part->physical_pos == physical_pos)
473
0
      return part;
474
0
    child = msg_part_find(part->children, physical_pos);
475
0
    if (child != NULL)
476
0
      return child;
477
0
  }
478
0
  return NULL;
479
0
}
480
481
static int
482
index_mail_get_binary_properties(struct mail *_mail,
483
         const struct message_part *part,
484
         bool include_hdr,
485
         struct mail_binary_properties *bprops_r)
486
0
{
487
0
  struct index_mail *mail = INDEX_MAIL(_mail);
488
0
  struct message_part *all_parts, *msg_part;
489
0
  const struct message_binary_part *bin_part, *root_bin_part;
490
0
  uoff_t size, end_offset;
491
0
  unsigned int lines;
492
0
  bool binary, converted;
493
494
0
  if (mail_get_parts(_mail, &all_parts) < 0)
495
0
    return -1;
496
497
  /* first lookup from cache */
498
0
  if (get_cached_binary_parts(mail)) {
499
0
    converted = (mail->data.bin_parts != NULL);
500
0
    binary = converted || message_parts_have_nuls(all_parts);
501
0
  } else {
502
    /* not found. parse the whole message */
503
0
    if (index_mail_read_binary_to_cache(_mail, all_parts, TRUE,
504
0
                "binary.size", &binary, &converted) < 0)
505
0
      return -1;
506
0
  }
507
508
0
  size = part->header_size.virtual_size +
509
0
    part->body_size.virtual_size;
510
  /* note that we assume here that binary translation doesn't change the
511
     headers' line counts. this isn't true if the original message
512
     contained duplicate Content-Transfer-Encoding lines, but since
513
     that's invalid anyway we don't bother trying to handle it. */
514
0
  lines = part->header_size.lines + part->body_size.lines;
515
0
  end_offset = part->physical_pos + size;
516
517
0
  bin_part = mail->data.bin_parts; root_bin_part = NULL;
518
0
  for (; bin_part != NULL; bin_part = bin_part->next) {
519
0
    msg_part = msg_part_find(all_parts, bin_part->physical_pos);
520
0
    if (msg_part == NULL) {
521
      /* either binary.parts or mime.parts is broken */
522
0
      mail_set_cache_corrupted(_mail, MAIL_FETCH_MESSAGE_PARTS, t_strdup_printf(
523
0
        "BINARY part at offset %"PRIuUOFF_T" not found from MIME parts",
524
0
        bin_part->physical_pos));
525
0
      return -1;
526
0
    }
527
0
    if (msg_part->physical_pos >= part->physical_pos &&
528
0
        msg_part->physical_pos < end_offset) {
529
0
      if (msg_part->physical_pos == part->physical_pos)
530
0
        root_bin_part = bin_part;
531
0
      size -= msg_part->header_size.virtual_size +
532
0
        msg_part->body_size.virtual_size;
533
0
      size += bin_part->binary_hdr_size +
534
0
        bin_part->binary_body_size;
535
0
      lines -= msg_part->body_size.lines;
536
0
      lines += bin_part->binary_body_lines_count;
537
0
    }
538
0
  }
539
0
  if (!include_hdr) {
540
0
    if (root_bin_part != NULL)
541
0
      size -= root_bin_part->binary_hdr_size;
542
0
    else
543
0
      size -= part->header_size.virtual_size;
544
0
    lines -= part->header_size.lines;
545
0
  }
546
547
0
  if (bprops_r != NULL) {
548
0
    bprops_r->size = size;
549
0
    bprops_r->lines = lines;
550
0
    bprops_r->binary = binary;
551
0
    bprops_r->converted = converted;
552
0
  }
553
0
  return 0;
554
0
}
555
556
int index_mail_get_binary_stream(struct mail *_mail,
557
         const struct message_part *part,
558
         bool include_hdr,
559
         struct mail_binary_properties *bprops_r,
560
         struct istream **stream_r)
561
0
{
562
0
  struct index_mail *mail = INDEX_MAIL(_mail);
563
0
  struct mail_binary_cache *cache = &_mail->box->storage->binary_cache;
564
0
  struct istream *input;
565
0
  bool binary, converted;
566
567
0
  if (stream_r == NULL) {
568
0
    return index_mail_get_binary_properties(_mail, part,
569
0
              include_hdr, bprops_r);
570
0
  }
571
572
  /* FIXME: always put the header to temp file. skip it when needed. */
573
0
  if (cache->box == _mail->box && cache->uid == _mail->uid &&
574
0
      cache->orig_physical_pos == part->physical_pos &&
575
0
      cache->include_hdr == include_hdr) {
576
    /* we have this cached already */
577
0
    i_stream_seek(cache->input, 0);
578
0
    timeout_reset(cache->to);
579
0
    binary = TRUE;
580
0
    converted = TRUE;
581
0
  } else {
582
0
    if (index_mail_read_binary_to_cache(_mail, part, include_hdr,
583
0
                "binary stream", &binary, &converted) < 0)
584
0
      return -1;
585
0
    mail->data.cache_fetch_fields |= MAIL_FETCH_STREAM_BINARY;
586
0
  }
587
0
  if (bprops_r != NULL) {
588
0
    bprops_r->size = cache->size;
589
    /* FIXME: lines is a bit complex to calculate in this code path,
590
       and current callers don't need it either. */
591
0
    bprops_r->lines = UINT_MAX;
592
0
    bprops_r->binary = binary;
593
0
    bprops_r->converted = converted;
594
0
  }
595
0
  if (!converted) {
596
    /* don't keep this cached. it's exactly the same as
597
       the original stream */
598
0
    i_assert(mail->data.stream != NULL);
599
0
    i_stream_seek(mail->data.stream, part->physical_pos +
600
0
            (include_hdr ? 0 :
601
0
             part->header_size.physical_size));
602
0
    input = i_stream_create_crlf(mail->data.stream);
603
0
    *stream_r = i_stream_create_limit(input, cache->size);
604
0
    i_stream_unref(&input);
605
0
    mail_storage_free_binary_cache(_mail->box->storage);
606
0
  } else {
607
0
    *stream_r = cache->input;
608
0
    i_stream_ref(cache->input);
609
0
  }
610
0
  return 0;
611
0
}