Coverage Report

Created: 2026-10-28 07:22

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib/istream-sized.c
Line
Count
Source
1
/* Copyright (c) Dovecot authors, see top-level COPYING file */
2
3
#include "lib.h"
4
#include "istream-private.h"
5
#include "istream-sized.h"
6
7
struct sized_istream {
8
  struct istream_private istream;
9
10
  istream_sized_callback_t *error_callback;
11
  void *error_context;
12
13
  uoff_t size;
14
  bool min_size_only;
15
};
16
17
static void i_stream_sized_destroy(struct iostream_private *stream)
18
17.2k
{
19
17.2k
  struct sized_istream *sstream =
20
17.2k
    container_of(stream, struct sized_istream, istream.iostream);
21
17.2k
  uoff_t v_offset;
22
23
17.2k
  v_offset = sstream->istream.parent_start_offset +
24
17.2k
    sstream->istream.istream.v_offset;
25
17.2k
  if (sstream->istream.parent->seekable ||
26
13.2k
      v_offset > sstream->istream.parent->v_offset) {
27
    /* get to same position in parent stream */
28
3.99k
    i_stream_seek(sstream->istream.parent, v_offset);
29
3.99k
  }
30
17.2k
}
31
32
static const char *
33
i_stream_create_sized_default_error_callback(
34
  const struct istream_sized_error_data *data, void *context ATTR_UNUSED)
35
352
{
36
352
  if (data->v_offset + data->new_bytes < data->wanted_size) {
37
352
    return t_strdup_printf("Stream is smaller than expected "
38
352
      "(%"PRIuUOFF_T" < %"PRIuUOFF_T")",
39
352
      data->v_offset + data->new_bytes, data->wanted_size);
40
352
  } else {
41
0
    return t_strdup_printf("Stream is larger than expected "
42
0
      "(%"PRIuUOFF_T" > %"PRIuUOFF_T", eof=%d)",
43
0
      data->v_offset + data->new_bytes, data->wanted_size,
44
0
      data->eof ? 1 : 0);
45
0
  }
46
352
}
47
48
static ssize_t
49
i_stream_sized_parent_read(struct istream_private *stream, size_t *pos_r)
50
38.7k
{
51
38.7k
  ssize_t ret;
52
53
38.7k
  do {
54
38.7k
    ret = i_stream_read_memarea(stream->parent);
55
38.7k
    stream->istream.stream_errno = stream->parent->stream_errno;
56
38.7k
    stream->istream.eof = stream->parent->eof;
57
38.7k
    stream->buffer = i_stream_get_data(stream->parent, pos_r);
58
38.7k
  } while (*pos_r <= stream->pos && ret > 0);
59
38.7k
  return ret;
60
38.7k
}
61
62
static ssize_t i_stream_sized_read(struct istream_private *stream)
63
31.3k
{
64
31.3k
  struct sized_istream *sstream =
65
31.3k
    container_of(stream, struct sized_istream, istream);
66
31.3k
  struct istream_sized_error_data data;
67
31.3k
  const char *error;
68
31.3k
  uoff_t left;
69
31.3k
  ssize_t ret;
70
31.3k
  size_t pos;
71
72
31.3k
  i_stream_seek(stream->parent, sstream->istream.parent_start_offset +
73
31.3k
          stream->istream.v_offset);
74
75
31.3k
  stream->pos -= stream->skip;
76
31.3k
  stream->skip = 0;
77
78
31.3k
  stream->buffer = i_stream_get_data(stream->parent, &pos);
79
31.3k
  if (pos > stream->pos)
80
0
    ret = 0;
81
31.3k
  else {
82
31.3k
    if ((ret = i_stream_sized_parent_read(stream, &pos)) == -2)
83
0
      return -2;
84
31.3k
  }
85
86
31.3k
  left = sstream->size - stream->istream.v_offset;
87
31.3k
  if (pos == left && ret != -1) {
88
    /* we have exactly the wanted amount of data left, but we
89
       don't know yet if there is more data in parent. */
90
7.39k
    ret = i_stream_sized_parent_read(stream, &pos);
91
7.39k
  }
92
93
31.3k
  i_zero(&data);
94
31.3k
  data.v_offset = stream->istream.v_offset;
95
31.3k
  data.new_bytes = pos;
96
31.3k
  data.wanted_size = sstream->size;
97
31.3k
  data.eof = stream->istream.eof;
98
99
31.3k
  if (pos == left) {
100
    /* we may or may not be finished, depending on whether
101
       parent is at EOF. */
102
23.9k
  } else if (pos > left) {
103
    /* parent has more data available than expected */
104
0
    if (!sstream->min_size_only) {
105
0
      error = sstream->error_callback(&data, sstream->error_context);
106
0
      io_stream_set_error(&stream->iostream, "%s", error);
107
0
      stream->istream.stream_errno = EINVAL;
108
0
      return -1;
109
0
    }
110
0
    pos = left;
111
0
    if (pos <= stream->pos) {
112
0
      stream->istream.eof = TRUE;
113
0
      ret = -1;
114
0
    }
115
7.39k
  } else if (!stream->istream.eof) {
116
    /* still more to read */
117
6.77k
  } else if (stream->istream.stream_errno != 0) {
118
    /* parent stream read() failed - preserve the error */
119
626
  } else {
120
    /* EOF before we reached the wanted size */
121
626
    error = sstream->error_callback(&data, sstream->error_context);
122
626
    io_stream_set_error(&stream->iostream, "%s", error);
123
626
    stream->istream.stream_errno = EPIPE;
124
626
  }
125
126
31.3k
  ret = pos > stream->pos ? (ssize_t)(pos - stream->pos) :
127
31.3k
    (ret == 0 ? 0 : -1);
128
31.3k
  stream->pos = pos;
129
31.3k
  i_assert(ret != -1 || stream->istream.eof ||
130
31.3k
     stream->istream.stream_errno != 0);
131
31.3k
  return ret;
132
31.3k
}
133
134
static int
135
i_stream_sized_stat(struct istream_private *stream, bool exact ATTR_UNUSED)
136
0
{
137
0
  struct sized_istream *sstream =
138
0
    container_of(stream, struct sized_istream, istream);
139
0
  const struct stat *st;
140
141
  /* parent stream may be base64-decoder. don't waste time decoding the
142
     entire stream, since we already know what the size is supposed
143
     to be. */
144
0
  if (i_stream_stat(stream->parent, FALSE, &st) < 0) {
145
0
    stream->istream.stream_errno = stream->parent->stream_errno;
146
0
    return -1;
147
0
  }
148
149
0
  stream->statbuf = *st;
150
0
  stream->statbuf.st_size = sstream->size;
151
0
  return 0;
152
0
}
153
154
static struct sized_istream *
155
i_stream_create_sized_common(struct istream *input, uoff_t size)
156
17.2k
{
157
17.2k
  struct sized_istream *sstream;
158
159
17.2k
  sstream = i_new(struct sized_istream, 1);
160
17.2k
  sstream->size = size;
161
17.2k
  sstream->istream.max_buffer_size = input->real_stream->max_buffer_size;
162
163
17.2k
  sstream->istream.iostream.destroy = i_stream_sized_destroy;
164
17.2k
  sstream->istream.read = i_stream_sized_read;
165
17.2k
  sstream->istream.stat = i_stream_sized_stat;
166
167
17.2k
  sstream->istream.istream.readable_fd = input->readable_fd;
168
17.2k
  sstream->istream.istream.blocking = input->blocking;
169
17.2k
  sstream->istream.istream.seekable = input->seekable;
170
17.2k
  (void)i_stream_create(&sstream->istream, input,
171
17.2k
            i_stream_get_fd(input),
172
17.2k
            ISTREAM_HIDDEN_INPUTS_NONE, 0);
173
17.2k
  return sstream;
174
17.2k
}
175
176
struct istream *i_stream_create_sized(struct istream *input, uoff_t size)
177
13.2k
{
178
13.2k
  struct sized_istream *sstream;
179
180
13.2k
  sstream = i_stream_create_sized_common(input, size);
181
13.2k
  sstream->error_callback = i_stream_create_sized_default_error_callback;
182
13.2k
  sstream->error_context = sstream;
183
13.2k
  return &sstream->istream.istream;
184
13.2k
}
185
186
struct istream *i_stream_create_sized_range(struct istream *input,
187
              uoff_t offset, uoff_t size)
188
0
{
189
0
  uoff_t orig_offset = input->v_offset;
190
0
  struct istream *ret;
191
192
0
  input->v_offset = offset;
193
0
  ret = i_stream_create_sized(input, size);
194
0
  input->v_offset = orig_offset;
195
0
  return ret;
196
0
}
197
198
struct istream *i_stream_create_min_sized(struct istream *input, uoff_t min_size)
199
13.2k
{
200
13.2k
  struct istream *ret;
201
202
13.2k
  ret= i_stream_create_sized(input, min_size);
203
13.2k
  struct sized_istream *ret_sstream =
204
13.2k
    container_of(ret->real_stream, struct sized_istream, istream);
205
13.2k
  ret_sstream->min_size_only = TRUE;
206
13.2k
  return ret;
207
13.2k
}
208
209
struct istream *i_stream_create_min_sized_range(struct istream *input,
210
            uoff_t offset, uoff_t min_size)
211
0
{
212
0
  struct istream *ret;
213
214
0
  ret = i_stream_create_sized_range(input, offset, min_size);
215
0
  struct sized_istream *ret_sstream =
216
0
    container_of(ret->real_stream, struct sized_istream, istream);
217
0
  ret_sstream->min_size_only = TRUE;
218
0
  return ret;
219
0
}
220
221
#undef i_stream_create_sized_with_callback
222
struct istream *
223
i_stream_create_sized_with_callback(struct istream *input, uoff_t size,
224
            istream_sized_callback_t *error_callback,
225
            void *context)
226
3.99k
{
227
3.99k
  struct sized_istream *sstream;
228
229
3.99k
  sstream = i_stream_create_sized_common(input, size);
230
3.99k
  sstream->error_callback = error_callback;
231
3.99k
  sstream->error_context = context;
232
3.99k
  return &sstream->istream.istream;
233
3.99k
}