Coverage Report

Created: 2026-09-03 07:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib-storage/index/mbox/istream-raw-mbox.c
Line
Count
Source
1
/* Copyright (c) Dovecot authors, see top-level COPYING file */
2
3
#include "lib.h"
4
#include "buffer.h"
5
#include "istream-private.h"
6
#include "istream-raw-mbox.h"
7
#include "mbox-from.h"
8
9
struct raw_mbox_istream {
10
  struct istream_private istream;
11
12
  time_t received_time, next_received_time;
13
  char *sender, *next_sender;
14
15
  uoff_t from_offset, hdr_offset, body_offset, mail_size;
16
  uoff_t input_peak_offset;
17
18
  bool locked:1;
19
  bool seeked:1;
20
  bool crlf_ending:1;
21
  bool corrupted:1;
22
  bool mail_size_forced:1;
23
  bool eof:1;
24
  bool header_missing_eoh:1;
25
};
26
27
static void mbox_istream_log_read_error(struct raw_mbox_istream *rstream)
28
0
{
29
0
  if (rstream->istream.parent->stream_errno != 0) {
30
    /* Log e.g. compression istream error */
31
0
    i_error("Failed to read mbox file %s: %s",
32
0
      i_stream_get_name(&rstream->istream.istream),
33
0
      i_stream_get_error(rstream->istream.parent));
34
0
  }
35
0
}
36
37
static void i_stream_raw_mbox_destroy(struct iostream_private *stream)
38
0
{
39
0
  struct raw_mbox_istream *rstream = (struct raw_mbox_istream *)stream;
40
41
0
  i_free(rstream->sender);
42
0
  i_free(rstream->next_sender);
43
44
0
  i_stream_seek(rstream->istream.parent,
45
0
          rstream->istream.istream.v_offset);
46
0
}
47
48
static int mbox_read_from_line(struct raw_mbox_istream *rstream)
49
0
{
50
0
  const unsigned char *buf, *p;
51
0
  char *sender;
52
0
  time_t received_time;
53
0
  size_t pos, line_pos;
54
0
  ssize_t ret;
55
0
  unsigned int skip;
56
0
  int tz;
57
58
0
  buf = i_stream_get_data(rstream->istream.parent, &pos);
59
0
  i_assert(pos > 0);
60
61
  /* from_offset points to "\nFrom ", so unless we're at the beginning
62
     of the file, skip the initial \n */
63
0
  if (rstream->from_offset == 0)
64
0
    skip = 0;
65
0
  else {
66
0
    skip = 1;
67
0
    if (*buf == '\r')
68
0
      skip++;
69
0
  }
70
71
0
  while ((p = memchr(buf+skip, '\n', pos-skip)) == NULL) {
72
0
    ret = i_stream_read_memarea(rstream->istream.parent);
73
0
    buf = i_stream_get_data(rstream->istream.parent, &pos);
74
0
    if (ret < 0) {
75
0
      if (ret == -2) {
76
        /* From_-line is too long, but we should be
77
           able to parse what we have so far. */
78
0
        break;
79
0
      }
80
      /* EOF shouldn't happen */
81
0
      rstream->istream.istream.eof =
82
0
        rstream->istream.parent->eof;
83
0
      rstream->istream.istream.stream_errno =
84
0
        rstream->istream.parent->stream_errno;
85
0
      return -1;
86
0
    }
87
0
    i_assert(pos > 0);
88
0
  }
89
0
  line_pos = p == NULL ? 0 : (size_t)(p - buf);
90
91
  /* beginning of mbox */
92
0
  if ((pos < skip+5) || memcmp(buf+skip, "From ", 5) != 0 ||
93
0
      mbox_from_parse((buf+skip)+5, (pos-skip)-5,
94
0
          &received_time, &tz, &sender) < 0) {
95
    /* broken From - should happen only at beginning of
96
       file if this isn't a mbox.. */
97
0
    io_stream_set_error(&rstream->istream.iostream,
98
0
      "mbox file doesn't begin with 'From ' line");
99
0
    rstream->istream.istream.stream_errno = EINVAL;
100
0
    return -1;
101
0
  }
102
103
0
  if (rstream->istream.istream.v_offset == rstream->from_offset) {
104
0
    rstream->received_time = received_time;
105
0
    i_free(rstream->sender);
106
0
    rstream->sender = sender;
107
0
  } else {
108
0
    rstream->next_received_time = received_time;
109
0
    i_free(rstream->next_sender);
110
0
    rstream->next_sender = sender;
111
0
  }
112
113
  /* skip over From-line */
114
0
  if (line_pos == 0) {
115
    /* line was too long. skip the input until we find LF. */
116
0
    rstream->istream.istream.v_offset += pos;
117
0
    i_stream_skip(rstream->istream.parent, pos);
118
119
0
    while ((ret = i_stream_read_memarea(rstream->istream.parent)) > 0) {
120
0
      p = memchr(buf, '\n', pos);
121
0
      if (p != NULL)
122
0
        break;
123
0
      rstream->istream.istream.v_offset += pos;
124
0
      i_stream_skip(rstream->istream.parent, pos);
125
0
    }
126
0
    if (ret <= 0) {
127
0
      i_assert(ret == -1);
128
      /* EOF shouldn't happen */
129
0
      rstream->istream.istream.eof =
130
0
        rstream->istream.parent->eof;
131
0
      rstream->istream.istream.stream_errno =
132
0
        rstream->istream.parent->stream_errno;
133
0
      return -1;
134
0
    }
135
0
    line_pos = (size_t)(p - buf);
136
0
  }
137
0
  rstream->istream.istream.v_offset += line_pos+1;
138
0
  i_stream_skip(rstream->istream.parent, line_pos+1);
139
140
0
  rstream->hdr_offset = rstream->istream.istream.v_offset;
141
0
  return 0;
142
0
}
143
144
static void handle_end_of_mail(struct raw_mbox_istream *rstream, size_t pos)
145
0
{
146
0
  rstream->mail_size = rstream->istream.istream.v_offset + pos -
147
0
    rstream->hdr_offset;
148
149
0
  if (rstream->hdr_offset + rstream->mail_size < rstream->body_offset) {
150
0
    uoff_t new_body_offset =
151
0
      rstream->hdr_offset + rstream->mail_size;
152
153
0
    if (rstream->body_offset != UOFF_T_MAX) {
154
      /* Header didn't have ending \n */
155
0
      rstream->header_missing_eoh = TRUE;
156
0
    } else {
157
      /* "headers\n\nFrom ..", the second \n belongs to next
158
         message which we didn't know at the time yet. */
159
0
    }
160
161
    /* The +2 check is for CR+LF linefeeds */
162
0
    i_assert(rstream->body_offset == UOFF_T_MAX ||
163
0
       rstream->body_offset == new_body_offset + 1 ||
164
0
       rstream->body_offset == new_body_offset + 2);
165
0
    rstream->body_offset = new_body_offset;
166
0
  }
167
0
}
168
169
static ssize_t i_stream_raw_mbox_read(struct istream_private *stream)
170
0
{
171
0
  static const char *mbox_from = "\nFrom ";
172
0
  struct raw_mbox_istream *rstream = (struct raw_mbox_istream *)stream;
173
0
  const unsigned char *buf;
174
0
  const char *fromp;
175
0
  char *sender;
176
0
  time_t received_time;
177
0
  size_t i, pos, new_pos, from_start_pos, from_after_pos;
178
0
  ssize_t ret = 0;
179
0
  int eoh_char, tz;
180
0
  bool crlf_ending = FALSE;
181
182
0
  i_assert(rstream->seeked);
183
0
  i_assert(stream->istream.v_offset >= rstream->from_offset);
184
185
0
  if (stream->istream.eof)
186
0
    return -1;
187
0
  if (rstream->corrupted) {
188
0
    rstream->istream.istream.stream_errno = EINVAL;
189
0
    return -1;
190
0
  }
191
192
0
  i_stream_seek(stream->parent, stream->istream.v_offset);
193
194
0
  stream->pos -= stream->skip;
195
0
  stream->skip = 0;
196
0
  stream->buffer = NULL;
197
198
0
  do {
199
0
    buf = i_stream_get_data(stream->parent, &pos);
200
0
    if (pos > 1 && stream->istream.v_offset + pos >
201
0
        rstream->input_peak_offset) {
202
      /* fake our read count. needed because if in the end
203
         we have only one character in buffer and we skip it
204
         (as potential CR), we want to get back to this
205
         i_stream_raw_mbox_read() to read more data. */
206
0
      ret = pos;
207
0
      break;
208
0
    }
209
0
    ret = i_stream_read_memarea(stream->parent);
210
0
  } while (ret > 0);
211
0
  stream->istream.stream_errno = stream->parent->stream_errno;
212
213
0
  if (ret < 0) {
214
0
    if (ret == -1)
215
0
      mbox_istream_log_read_error(rstream);
216
0
    if (ret == -2) {
217
0
      if (stream->skip == stream->pos) {
218
        /* From_-line is longer than our input buffer.
219
           finish the check without seeing the LF. */
220
0
      } else if (stream->istream.v_offset + pos ==
221
0
           rstream->input_peak_offset) {
222
        /* we've read everything our parent stream
223
           has to offer. */
224
0
        stream->buffer = buf;
225
0
        return -2;
226
0
      }
227
      /* parent stream is full, but we haven't returned
228
         all its bytes to our caller yet. */
229
0
    } else if (stream->istream.v_offset != 0 || pos == 0) {
230
      /* we've read the whole file, final byte should be
231
         the \n trailer */
232
0
      if (pos > 0 && buf[pos-1] == '\n') {
233
0
        pos--;
234
0
        if (pos > 0 && buf[pos-1] == '\r') {
235
0
          crlf_ending = TRUE;
236
0
          pos--;
237
0
        }
238
0
      }
239
240
0
      i_assert(pos >= stream->pos);
241
0
      ret = pos == stream->pos ? -1 :
242
0
        (ssize_t)(pos - stream->pos);
243
244
0
      stream->buffer = buf;
245
0
      stream->pos = pos;
246
247
0
      if (stream->istream.v_offset == rstream->from_offset) {
248
        /* haven't seen From-line yet, so this mbox
249
           stream is now at EOF */
250
0
        rstream->eof = TRUE;
251
0
      }
252
0
      stream->istream.eof = TRUE;
253
0
      rstream->crlf_ending = crlf_ending;
254
0
      handle_end_of_mail(rstream, pos);
255
0
      return ret < 0 ? i_stream_raw_mbox_read(stream) : ret;
256
0
    }
257
0
  }
258
259
0
  if (stream->istream.v_offset == rstream->from_offset) {
260
    /* beginning of message, we haven't yet read our From-line */
261
0
    if (pos == 2 && ret > 0) {
262
      /* we're at the end of file with CR+LF linefeeds?
263
         need more data to verify it. */
264
0
      rstream->input_peak_offset =
265
0
        stream->istream.v_offset + pos;
266
0
      return i_stream_raw_mbox_read(stream);
267
0
    }
268
0
    if (mbox_read_from_line(rstream) < 0) {
269
0
      io_stream_set_error(&stream->iostream,
270
0
        "Next message unexpectedly corrupted in mbox file "
271
0
        "%s at %"PRIuUOFF_T,
272
0
        i_stream_get_name(&stream->istream),
273
0
        stream->istream.v_offset);
274
0
      if (stream->istream.v_offset != 0)
275
0
        i_error("%s", stream->iostream.error);
276
0
      stream->pos = 0;
277
0
      rstream->eof = TRUE;
278
0
      rstream->corrupted = TRUE;
279
0
      return -1;
280
0
    }
281
282
    /* got it. we don't want to return it however,
283
       so start again from headers */
284
0
    buf = i_stream_get_data(stream->parent, &pos);
285
0
    if (pos == 0)
286
0
      return i_stream_raw_mbox_read(stream);
287
0
  }
288
289
  /* See if we have From-line here - note that it works right only
290
     because all characters are different in mbox_from. */
291
0
        fromp = mbox_from; from_start_pos = from_after_pos = SIZE_MAX;
292
0
  eoh_char = rstream->body_offset == UOFF_T_MAX ? '\n' : -1;
293
0
  for (i = stream->pos; i < pos; i++) {
294
0
    if (buf[i] == eoh_char &&
295
0
        ((i > 0 && buf[i-1] == '\n') ||
296
0
                     (i > 1 && buf[i-1] == '\r' && buf[i-2] == '\n') ||
297
0
         stream->istream.v_offset + i == rstream->hdr_offset)) {
298
0
      rstream->body_offset = stream->istream.v_offset + i + 1;
299
0
      eoh_char = -1;
300
0
    }
301
0
    if ((char)buf[i] == *fromp) {
302
0
      if (*++fromp == '\0') {
303
        /* potential From-line, see if we have the
304
           rest of the line buffered. */
305
0
        i++;
306
0
        if (i >= 7 && buf[i-7] == '\r') {
307
          /* CR also belongs to it. */
308
0
          crlf_ending = TRUE;
309
0
          from_start_pos = i - 7;
310
0
        } else {
311
0
          crlf_ending = FALSE;
312
0
          from_start_pos = i - 6;
313
0
        }
314
315
0
        if (rstream->mail_size == UOFF_T_MAX ||
316
0
            rstream->hdr_offset + rstream->mail_size ==
317
0
            stream->istream.v_offset + from_start_pos) {
318
0
          from_after_pos = i;
319
0
          if (ret == -2) {
320
            /* even if we don't have the
321
               whole line, we need to
322
               finish this check now. */
323
0
            goto mbox_verify;
324
0
          }
325
0
        }
326
0
        fromp = mbox_from;
327
0
      } else if (from_after_pos != SIZE_MAX) {
328
        /* we have the whole From-line here now.
329
           See if it's a valid one. */
330
0
      mbox_verify:
331
0
        if (mbox_from_parse(buf + from_after_pos,
332
0
                pos - from_after_pos,
333
0
                &received_time, &tz,
334
0
                &sender) == 0) {
335
          /* yep, we stop here. */
336
0
          rstream->next_received_time =
337
0
            received_time;
338
0
          i_free(rstream->next_sender);
339
0
          rstream->next_sender = sender;
340
0
          stream->istream.eof = TRUE;
341
342
0
          rstream->crlf_ending = crlf_ending;
343
0
          handle_end_of_mail(rstream,
344
0
                 from_start_pos);
345
0
          break;
346
0
        }
347
0
        from_after_pos = SIZE_MAX;
348
0
      }
349
0
    } else {
350
0
      fromp = mbox_from;
351
0
      if ((char)buf[i] == *fromp)
352
0
        fromp++;
353
0
    }
354
0
  }
355
356
  /* we want to go at least one byte further next time */
357
0
  rstream->input_peak_offset = stream->istream.v_offset + i;
358
359
0
  if (from_after_pos != SIZE_MAX) {
360
    /* we're waiting for the \n at the end of From-line */
361
0
    new_pos = from_start_pos;
362
0
  } else {
363
    /* leave out the beginnings of potential From-line + CR */
364
0
    new_pos = i - (fromp - mbox_from);
365
0
    if (new_pos > 0)
366
0
      new_pos--;
367
0
  }
368
369
0
  if (stream->istream.v_offset -
370
0
      rstream->hdr_offset + new_pos > rstream->mail_size) {
371
    /* istream_raw_mbox_set_next_offset() used invalid
372
       cached next_offset? */
373
0
    io_stream_set_error(&stream->iostream,
374
0
      "Next message unexpectedly lost from mbox file "
375
0
      "%s at %"PRIuUOFF_T" (%s)",
376
0
      i_stream_get_name(&stream->istream),
377
0
      rstream->hdr_offset + rstream->mail_size,
378
0
      rstream->mail_size_forced ? "cached" : "noncached");
379
0
    i_error("%s", stream->iostream.error);
380
0
    rstream->eof = TRUE;
381
0
    rstream->corrupted = TRUE;
382
0
    rstream->istream.istream.stream_errno = EINVAL;
383
0
    stream->pos = 0;
384
0
    return -1;
385
0
  }
386
387
0
  stream->buffer = buf;
388
0
  if (new_pos == stream->pos) {
389
0
    if (stream->istream.eof || ret > 0)
390
0
      return i_stream_raw_mbox_read(stream);
391
0
    i_assert(new_pos > 0);
392
0
    ret = -2;
393
0
  } else {
394
0
    i_assert(new_pos > stream->pos);
395
0
    ret = new_pos - stream->pos;
396
0
    stream->pos = new_pos;
397
0
  }
398
0
  return ret;
399
0
}
400
401
static void i_stream_raw_mbox_seek(struct istream_private *stream,
402
           uoff_t v_offset, bool mark ATTR_UNUSED)
403
0
{
404
0
  struct raw_mbox_istream *rstream = (struct raw_mbox_istream *)stream;
405
406
0
  stream->istream.v_offset = v_offset;
407
0
  stream->skip = stream->pos = 0;
408
0
  stream->buffer = NULL;
409
410
0
        rstream->input_peak_offset = 0;
411
0
  rstream->eof = FALSE;
412
0
}
413
414
static void i_stream_raw_mbox_sync(struct istream_private *stream)
415
0
{
416
0
  struct raw_mbox_istream *rstream = (struct raw_mbox_istream *)stream;
417
418
0
  i_stream_sync(stream->parent);
419
420
0
  rstream->istream.skip = 0;
421
0
  rstream->istream.pos = 0;
422
0
  rstream->input_peak_offset = 0;
423
0
}
424
425
static int
426
i_stream_raw_mbox_stat(struct istream_private *stream, bool exact)
427
0
{
428
0
  const struct stat *st;
429
0
  struct raw_mbox_istream *rstream = (struct raw_mbox_istream *)stream;
430
431
0
  if (i_stream_stat(stream->parent, exact, &st) < 0) {
432
0
    stream->istream.stream_errno = stream->parent->stream_errno;
433
0
    return -1;
434
0
  }
435
436
0
  stream->statbuf = *st;
437
0
  stream->statbuf.st_size =
438
0
    !exact && rstream->seeked && rstream->mail_size != UOFF_T_MAX ?
439
0
    (off_t)rstream->mail_size : -1;
440
0
  return 0;
441
0
}
442
443
struct istream *i_stream_create_raw_mbox(struct istream *input)
444
0
{
445
0
  struct raw_mbox_istream *rstream;
446
447
0
  i_assert(input->v_offset == 0);
448
449
0
  rstream = i_new(struct raw_mbox_istream, 1);
450
451
0
  rstream->body_offset = UOFF_T_MAX;
452
0
  rstream->mail_size = UOFF_T_MAX;
453
0
  rstream->received_time = (time_t)-1;
454
0
  rstream->next_received_time = (time_t)-1;
455
456
0
  rstream->istream.iostream.destroy = i_stream_raw_mbox_destroy;
457
0
  rstream->istream.max_buffer_size = input->real_stream->max_buffer_size;
458
0
  rstream->istream.read = i_stream_raw_mbox_read;
459
0
  rstream->istream.seek = i_stream_raw_mbox_seek;
460
0
  rstream->istream.sync = i_stream_raw_mbox_sync;
461
0
  rstream->istream.stat = i_stream_raw_mbox_stat;
462
463
0
  rstream->istream.istream.readable_fd = input->readable_fd;
464
0
  rstream->istream.istream.blocking = input->blocking;
465
0
  rstream->istream.istream.seekable = input->seekable;
466
467
0
  return i_stream_create(&rstream->istream, input, -1,
468
0
             ISTREAM_HIDDEN_INPUTS_NONE, 0);
469
0
}
470
471
static int istream_raw_mbox_is_valid_from(struct raw_mbox_istream *rstream)
472
0
{
473
0
  const unsigned char *data;
474
0
  size_t size = 0;
475
0
  time_t received_time;
476
0
  char *sender;
477
0
  int tz;
478
0
  ssize_t ret = 0;
479
480
  /* minimal: "From x Thu Nov 29 22:33:52 2001" = 31 chars */
481
0
  do {
482
0
    data = i_stream_get_data(rstream->istream.parent, &size);
483
0
    if (size >= 31)
484
0
      break;
485
0
  } while ((ret = i_stream_read_memarea(rstream->istream.parent)) > 0);
486
0
  if (ret == -1)
487
0
    mbox_istream_log_read_error(rstream);
488
489
0
  if ((size == 1 && data[0] == '\n') ||
490
0
      (size == 2 && data[0] == '\r' && data[1] == '\n')) {
491
    /* EOF */
492
0
    return 1;
493
0
  }
494
495
0
  if (size > 31 && memcmp(data, "\nFrom ", 6) == 0) {
496
0
    data += 6;
497
0
    size -= 6;
498
0
  } else if (size > 32 && memcmp(data, "\r\nFrom ", 7) == 0) {
499
0
    data += 7;
500
0
    size -= 7;
501
0
  } else {
502
0
    return 0;
503
0
  }
504
505
0
  while (memchr(data, '\n', size) == NULL) {
506
0
    ret = i_stream_read_bytes(rstream->istream.parent,
507
0
            &data, &size, size+1);
508
0
    if (ret < 0) {
509
0
      if (ret == -1)
510
0
        mbox_istream_log_read_error(rstream);
511
0
      break;
512
0
    }
513
0
  }
514
515
0
  if (mbox_from_parse(data, size, &received_time, &tz, &sender) < 0)
516
0
    return 0;
517
518
0
  rstream->next_received_time = received_time;
519
0
  i_free(rstream->next_sender);
520
0
  rstream->next_sender = sender;
521
0
  return 1;
522
0
}
523
524
uoff_t istream_raw_mbox_get_start_offset(struct istream *stream)
525
0
{
526
0
  struct raw_mbox_istream *rstream =
527
0
    (struct raw_mbox_istream *)stream->real_stream;
528
529
0
  i_assert(rstream->seeked);
530
531
0
  return rstream->from_offset;
532
0
}
533
534
int istream_raw_mbox_get_header_offset(struct istream *stream,
535
               uoff_t *hdr_offset_r)
536
0
{
537
0
  struct raw_mbox_istream *rstream =
538
0
    (struct raw_mbox_istream *)stream->real_stream;
539
540
0
  i_assert(rstream->seeked);
541
542
0
  if (rstream->hdr_offset == rstream->from_offset)
543
0
    (void)i_stream_read(stream);
544
545
0
  if (rstream->corrupted) {
546
0
    i_error("Unexpectedly lost From-line from mbox file %s at "
547
0
      "%"PRIuUOFF_T, i_stream_get_name(stream),
548
0
      rstream->from_offset);
549
0
    return -1;
550
0
  }
551
0
  if (stream->stream_errno != 0)
552
0
    return -1;
553
554
0
  *hdr_offset_r = rstream->hdr_offset;
555
0
  return 0;
556
0
}
557
558
int istream_raw_mbox_get_body_offset(struct istream *stream,
559
             uoff_t *body_offset_r)
560
0
{
561
0
  struct raw_mbox_istream *rstream =
562
0
    (struct raw_mbox_istream *)stream->real_stream;
563
0
  uoff_t offset;
564
565
0
  i_assert(rstream->seeked);
566
567
0
  if (rstream->body_offset != UOFF_T_MAX) {
568
0
    *body_offset_r = rstream->body_offset;
569
0
    return 0;
570
0
  }
571
572
0
  offset = stream->v_offset;
573
0
  i_stream_seek(stream, rstream->hdr_offset);
574
0
  while (rstream->body_offset == UOFF_T_MAX) {
575
0
    i_stream_skip(stream, i_stream_get_data_size(stream));
576
577
0
    if (i_stream_read(stream) < 0) {
578
0
      if (rstream->corrupted) {
579
0
        i_error("Unexpectedly lost From-line from mbox file "
580
0
          "%s at %"PRIuUOFF_T,
581
0
          i_stream_get_name(stream),
582
0
          rstream->from_offset);
583
0
      } else {
584
0
        i_assert(rstream->body_offset != UOFF_T_MAX);
585
0
      }
586
0
      return -1;
587
0
    }
588
0
  }
589
590
0
  i_stream_seek(stream, offset);
591
0
  *body_offset_r = rstream->body_offset;
592
0
  return 0;
593
0
}
594
595
int istream_raw_mbox_get_body_size(struct istream *stream,
596
           uoff_t expected_body_size,
597
           uoff_t *body_size_r)
598
0
{
599
0
  struct raw_mbox_istream *rstream =
600
0
    (struct raw_mbox_istream *)stream->real_stream;
601
0
  const unsigned char *data;
602
0
  size_t size;
603
0
  uoff_t old_offset, body_offset, body_size, next_body_offset;
604
605
0
  i_assert(rstream->seeked);
606
0
  i_assert(rstream->hdr_offset != UOFF_T_MAX);
607
608
0
  if (istream_raw_mbox_get_body_offset(stream, &body_offset) < 0)
609
0
    return -1;
610
0
  body_size = rstream->mail_size == UOFF_T_MAX ? UOFF_T_MAX :
611
0
    rstream->mail_size - (rstream->body_offset -
612
0
              rstream->hdr_offset);
613
0
  old_offset = stream->v_offset;
614
0
  if (expected_body_size != UOFF_T_MAX) {
615
    /* if we already have the existing body size, use it as long as
616
       it's >= expected body_size. otherwise the previous parsing
617
       may have stopped at a From_-line that belongs to the body. */
618
0
    if (body_size != UOFF_T_MAX && body_size >= expected_body_size) {
619
0
      *body_size_r = body_size;
620
0
      return 0;
621
0
    }
622
623
0
    next_body_offset = rstream->body_offset + expected_body_size;
624
    /* If header_missing_eoh is set, the message body begins with
625
       a From_-line and the body_offset is pointing to the line
626
       *before* the first line of the body, i.e. the empty line
627
       separating the headers from the body. If that is the case,
628
       we'll have to skip over the empty line to get the correct
629
       next_body_offset. */
630
0
    if (rstream->header_missing_eoh) {
631
0
      i_assert(body_size == 0);
632
0
      next_body_offset += rstream->crlf_ending ? 2 : 1;
633
0
    }
634
635
0
    i_stream_seek(rstream->istream.parent, next_body_offset);
636
0
    if (istream_raw_mbox_is_valid_from(rstream) > 0) {
637
0
      rstream->mail_size =
638
0
        next_body_offset - rstream->hdr_offset;
639
0
      i_stream_seek(stream, old_offset);
640
0
      *body_size_r = expected_body_size;
641
0
      return 0;
642
0
    }
643
    /* invalid expected_body_size */
644
0
  }
645
0
  if (body_size != UOFF_T_MAX) {
646
0
    *body_size_r = body_size;
647
0
    return 0;
648
0
  }
649
650
  /* have to read through the message body */
651
0
  while (i_stream_read_more(stream, &data, &size) > 0)
652
0
    i_stream_skip(stream, size);
653
0
  i_stream_seek(stream, old_offset);
654
0
  if (stream->stream_errno != 0)
655
0
    return -1;
656
657
0
  i_assert(rstream->mail_size != UOFF_T_MAX);
658
0
  *body_size_r = rstream->mail_size -
659
0
    (rstream->body_offset - rstream->hdr_offset);
660
0
  return 0;
661
0
}
662
663
time_t istream_raw_mbox_get_received_time(struct istream *stream)
664
0
{
665
0
  struct raw_mbox_istream *rstream =
666
0
    (struct raw_mbox_istream *)stream->real_stream;
667
668
0
  i_assert(rstream->seeked);
669
670
0
  if (rstream->received_time == (time_t)-1)
671
0
    (void)i_stream_read(stream);
672
0
  return rstream->received_time;
673
0
}
674
675
const char *istream_raw_mbox_get_sender(struct istream *stream)
676
0
{
677
0
  struct raw_mbox_istream *rstream =
678
0
    (struct raw_mbox_istream *)stream->real_stream;
679
680
0
  i_assert(rstream->seeked);
681
682
0
  if (rstream->sender == NULL)
683
0
    (void)i_stream_read(stream);
684
0
  return rstream->sender == NULL ? "" : rstream->sender;
685
0
}
686
687
bool istream_raw_mbox_has_crlf_ending(struct istream *stream)
688
0
{
689
0
  struct raw_mbox_istream *rstream =
690
0
    (struct raw_mbox_istream *)stream->real_stream;
691
692
0
  i_assert(rstream->seeked);
693
694
0
  return rstream->crlf_ending;
695
0
}
696
697
int istream_raw_mbox_next(struct istream *stream, uoff_t expected_body_size)
698
0
{
699
0
  struct raw_mbox_istream *rstream =
700
0
    (struct raw_mbox_istream *)stream->real_stream;
701
0
  uoff_t body_size;
702
703
0
  if (istream_raw_mbox_get_body_size(stream, expected_body_size,
704
0
             &body_size) < 0)
705
0
    return -1;
706
0
  rstream->mail_size = UOFF_T_MAX;
707
708
0
  rstream->received_time = rstream->next_received_time;
709
0
  rstream->next_received_time = (time_t)-1;
710
711
0
  i_free(rstream->sender);
712
0
  rstream->sender = rstream->next_sender;
713
0
  rstream->next_sender = NULL;
714
715
0
  rstream->from_offset = rstream->body_offset + body_size;
716
0
  rstream->hdr_offset = rstream->from_offset;
717
0
  rstream->body_offset = UOFF_T_MAX;
718
0
  rstream->header_missing_eoh = FALSE;
719
720
0
  if (stream->v_offset != rstream->from_offset)
721
0
    i_stream_seek_mark(stream, rstream->from_offset);
722
0
  i_stream_seek_mark(rstream->istream.parent, rstream->from_offset);
723
724
0
  rstream->eof = FALSE;
725
0
  rstream->istream.istream.eof = FALSE;
726
0
  return 0;
727
0
}
728
729
int istream_raw_mbox_seek(struct istream *stream, uoff_t offset)
730
0
{
731
0
  struct raw_mbox_istream *rstream =
732
0
    (struct raw_mbox_istream *)stream->real_stream;
733
0
  bool check;
734
735
0
  i_assert(rstream->locked);
736
737
  /* reset any (corruption) errors */
738
0
  stream->stream_errno = 0;
739
0
  i_free_and_null(stream->real_stream->iostream.error);
740
0
  rstream->corrupted = FALSE;
741
0
  rstream->eof = FALSE;
742
0
  rstream->istream.istream.eof = FALSE;
743
744
  /* if seeked is FALSE, we unlocked in the middle. don't try to use
745
     any cached state then. */
746
0
  if (rstream->mail_size != UOFF_T_MAX && rstream->seeked &&
747
0
      rstream->hdr_offset + rstream->mail_size == offset)
748
0
    return istream_raw_mbox_next(stream, UOFF_T_MAX);
749
750
0
  if (offset == rstream->from_offset && rstream->seeked) {
751
    /* back to beginning of current message */
752
0
    offset = rstream->hdr_offset;
753
0
    check = offset == 0;
754
0
  } else {
755
0
    rstream->body_offset = UOFF_T_MAX;
756
0
    rstream->mail_size = UOFF_T_MAX;
757
0
    rstream->received_time = (time_t)-1;
758
0
    rstream->next_received_time = (time_t)-1;
759
0
    rstream->header_missing_eoh = FALSE;
760
761
0
    i_free(rstream->sender);
762
0
    rstream->sender = NULL;
763
0
    i_free(rstream->next_sender);
764
0
    rstream->next_sender = NULL;
765
766
0
                rstream->from_offset = offset;
767
0
    rstream->hdr_offset = offset;
768
0
    check = TRUE;
769
0
  }
770
0
  rstream->seeked = TRUE;
771
772
0
  i_stream_seek_mark(stream, offset);
773
0
  i_stream_seek_mark(rstream->istream.parent, offset);
774
775
0
  if (check)
776
0
    (void)i_stream_read(stream);
777
0
  return rstream->corrupted ? -1 : 0;
778
0
}
779
780
void istream_raw_mbox_set_next_offset(struct istream *stream, uoff_t offset)
781
0
{
782
0
  struct raw_mbox_istream *rstream =
783
0
    (struct raw_mbox_istream *)stream->real_stream;
784
785
0
  i_assert(rstream->hdr_offset != UOFF_T_MAX);
786
787
0
  rstream->mail_size_forced = TRUE;
788
0
  rstream->mail_size = offset - rstream->hdr_offset;
789
0
}
790
791
bool istream_raw_mbox_is_eof(struct istream *stream)
792
0
{
793
0
  struct raw_mbox_istream *rstream =
794
0
    (struct raw_mbox_istream *)stream->real_stream;
795
796
0
  return rstream->eof;
797
0
}
798
799
bool istream_raw_mbox_is_corrupted(struct istream *stream)
800
0
{
801
0
  struct raw_mbox_istream *rstream =
802
0
    (struct raw_mbox_istream *)stream->real_stream;
803
804
0
  return rstream->corrupted;
805
0
}
806
807
void istream_raw_mbox_set_locked(struct istream *stream)
808
0
{
809
0
  struct raw_mbox_istream *rstream =
810
0
    (struct raw_mbox_istream *)stream->real_stream;
811
812
0
  rstream->locked = TRUE;
813
0
}
814
815
void istream_raw_mbox_set_unlocked(struct istream *stream)
816
0
{
817
0
  struct raw_mbox_istream *rstream =
818
0
    (struct raw_mbox_istream *)stream->real_stream;
819
820
0
  rstream->locked = FALSE;
821
0
  rstream->seeked = FALSE;
822
0
}