Coverage Report

Created: 2026-08-31 07:00

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib-storage/index/dbox-common/dbox-mail.c
Line
Count
Source
1
/* Copyright (c) Dovecot authors, see top-level COPYING file */
2
3
#include "lib.h"
4
#include "istream.h"
5
#include "str.h"
6
#include "index-storage.h"
7
#include "index-mail.h"
8
#include "index-pop3-uidl.h"
9
#include "dbox-attachment.h"
10
#include "dbox-storage.h"
11
#include "dbox-file.h"
12
#include "dbox-mail.h"
13
14
15
struct mail *
16
dbox_mail_alloc(struct mailbox_transaction_context *t,
17
    enum mail_fetch_field wanted_fields,
18
    struct mailbox_header_lookup_ctx *wanted_headers)
19
0
{
20
0
  struct dbox_mail *mail;
21
0
  pool_t pool;
22
23
0
  pool = pool_alloconly_create("mail", 2048);
24
0
  mail = p_new(pool, struct dbox_mail, 1);
25
26
0
  index_mail_init(&mail->imail, t, wanted_fields, wanted_headers, pool, NULL);
27
0
  return &mail->imail.mail.mail;
28
0
}
29
30
void dbox_mail_close(struct mail *_mail)
31
0
{
32
0
  struct dbox_mail *mail = DBOX_MAIL(_mail);
33
34
0
  index_mail_close(_mail);
35
  /* close the dbox file only after index is closed, since it may still
36
     try to read from it. */
37
0
  if (mail->open_file != NULL)
38
0
    dbox_file_unref(&mail->open_file);
39
0
}
40
41
int dbox_mail_metadata_read(struct dbox_mail *mail, struct dbox_file **file_r)
42
0
{
43
0
  struct dbox_storage *storage =
44
0
    DBOX_STORAGE(mail->imail.mail.mail.box->storage);
45
0
  uoff_t offset;
46
47
0
  if (storage->v.mail_open(mail, &offset, file_r) < 0)
48
0
    return -1;
49
50
0
  if (dbox_file_seek(*file_r, offset) <= 0)
51
0
    return -1;
52
0
  if (dbox_file_metadata_read(*file_r) <= 0)
53
0
    return -1;
54
55
0
  if (mail->imail.data.stream != NULL) {
56
    /* we just messed up mail's input stream by reading metadata */
57
0
    i_stream_seek((*file_r)->input, offset);
58
0
    i_stream_sync(mail->imail.data.stream);
59
0
  } else
60
0
    mail_metadata_accessed_event(mail_event(&mail->imail.mail.mail));
61
62
0
  return 0;
63
0
}
64
65
static int
66
dbox_mail_metadata_get(struct dbox_mail *mail, enum dbox_metadata_key key,
67
           const char **value_r)
68
0
{
69
0
  struct dbox_file *file;
70
71
0
  if (dbox_mail_metadata_read(mail, &file) < 0)
72
0
    return -1;
73
74
0
  *value_r = dbox_file_metadata_get(file, key);
75
0
  return 0;
76
0
}
77
78
int dbox_mail_get_physical_size(struct mail *_mail, uoff_t *size_r)
79
0
{
80
0
  struct dbox_mail *mail = DBOX_MAIL(_mail);
81
0
  struct index_mail_data *data = &mail->imail.data;
82
0
  struct dbox_file *file;
83
84
0
  if (index_mail_get_physical_size(_mail, size_r) == 0)
85
0
    return 0;
86
87
0
  if (dbox_mail_metadata_read(mail, &file) < 0)
88
0
    return -1;
89
90
0
  data->physical_size = dbox_file_get_plaintext_size(file);
91
0
  *size_r = data->physical_size;
92
0
  return 0;
93
0
}
94
95
int dbox_mail_get_virtual_size(struct mail *_mail, uoff_t *size_r)
96
0
{
97
0
  struct dbox_mail *mail = DBOX_MAIL(_mail);
98
0
  struct index_mail_data *data = &mail->imail.data;
99
0
  const char *value;
100
0
  uintmax_t size;
101
102
0
  if (index_mail_get_cached_virtual_size(&mail->imail, size_r))
103
0
    return 0;
104
105
0
  if (dbox_mail_metadata_get(mail, DBOX_METADATA_VIRTUAL_SIZE,
106
0
           &value) < 0)
107
0
    return -1;
108
0
  if (value == NULL)
109
0
    return index_mail_get_virtual_size(_mail, size_r);
110
111
0
  if (str_to_uintmax_hex(value, &size) < 0 || size > UOFF_T_MAX)
112
0
    return -1;
113
0
  data->virtual_size = (uoff_t)size;
114
0
  *size_r = data->virtual_size;
115
0
  return 0;
116
0
}
117
118
int dbox_mail_get_received_date(struct mail *_mail, time_t *date_r)
119
0
{
120
0
  struct dbox_mail *mail = DBOX_MAIL(_mail);
121
0
  struct index_mail_data *data = &mail->imail.data;
122
0
  const char *value;
123
0
  uintmax_t time;
124
125
0
  if (index_mail_get_received_date(_mail, date_r) == 0)
126
0
    return 0;
127
128
0
  if (dbox_mail_metadata_get(mail, DBOX_METADATA_RECEIVED_TIME,
129
0
           &value) < 0)
130
0
    return -1;
131
132
0
  time = 0;
133
0
  if (value != NULL && str_to_uintmax_hex(value, &time) < 0)
134
0
    return -1;
135
136
0
  data->received_date = (time_t)time;
137
0
  *date_r = data->received_date;
138
0
  return 0;
139
0
}
140
141
int dbox_mail_get_save_date(struct mail *_mail, time_t *date_r)
142
0
{
143
0
  struct dbox_storage *storage = DBOX_STORAGE(_mail->box->storage);
144
0
  struct dbox_mail *mail = DBOX_MAIL(_mail);
145
0
  struct index_mail_data *data = &mail->imail.data;
146
0
  struct stat st;
147
148
0
  if (index_mail_get_save_date(_mail, date_r) > 0)
149
0
    return 1;
150
151
0
  if (storage->v.mail_file_set(mail) < 0)
152
0
    return -1;
153
154
0
  _mail->transaction->stats.stat_lookup_count++;
155
0
  if (dbox_file_stat(mail->open_file,
156
0
      mail_event(&mail->imail.mail.mail), &st) < 0) {
157
0
    if (errno == ENOENT)
158
0
      mail_set_expunged(_mail);
159
0
    return -1;
160
0
  }
161
0
  *date_r = data->save_date = st.st_ctime;
162
0
  return 1;
163
0
}
164
165
static int
166
dbox_get_cached_metadata(struct dbox_mail *mail, enum dbox_metadata_key key,
167
       enum index_cache_field cache_field,
168
       const char **value_r)
169
0
{
170
0
  struct index_mail *imail = &mail->imail;
171
0
  struct index_mailbox_context *ibox =
172
0
    INDEX_STORAGE_CONTEXT(imail->mail.mail.box);
173
0
  const char *value;
174
0
  string_t *str;
175
0
  uint32_t order;
176
177
0
  str = str_new(imail->mail.data_pool, 64);
178
0
  if (mail_cache_lookup_field(imail->mail.mail.transaction->cache_view,
179
0
            str, imail->mail.mail.seq,
180
0
            ibox->cache_fields[cache_field].idx) > 0) {
181
0
    if (cache_field == MAIL_CACHE_POP3_ORDER) {
182
0
      i_assert(str_len(str) == sizeof(order));
183
0
      memcpy(&order, str_data(str), sizeof(order));
184
0
      str_truncate(str, 0);
185
0
      if (order != 0)
186
0
        str_printfa(str, "%u", order);
187
0
      else {
188
        /* order=0 means it doesn't exist. we don't
189
           want to return "0" though, because then the
190
           mails get ordered to beginning, while
191
           nonexistent are supposed to be ordered at
192
           the end. */
193
0
      }
194
0
    }
195
0
    *value_r = str_c(str);
196
0
    return 0;
197
0
  }
198
199
0
  if (dbox_mail_metadata_get(mail, key, &value) < 0)
200
0
    return -1;
201
202
0
  if (value == NULL)
203
0
    value = "";
204
0
  if (cache_field != MAIL_CACHE_POP3_ORDER) {
205
0
    index_mail_cache_add_idx(imail, ibox->cache_fields[cache_field].idx,
206
0
           value, strlen(value));
207
0
  } else {
208
0
    if (str_to_uint(value, &order) < 0)
209
0
      order = 0;
210
0
    index_mail_cache_add_idx(imail, ibox->cache_fields[cache_field].idx,
211
0
           &order, sizeof(order));
212
0
  }
213
214
  /* don't return pointer to dbox metadata directly, since it may
215
     change unexpectedly */
216
0
  str_truncate(str, 0);
217
0
  str_append(str, value);
218
0
  *value_r = str_c(str);
219
0
  return 0;
220
0
}
221
222
int dbox_mail_get_special(struct mail *_mail, enum mail_fetch_field field,
223
        const char **value_r)
224
0
{
225
0
  struct dbox_mail *mail = DBOX_MAIL(_mail);
226
0
  int ret;
227
228
  /* keep the UIDL in cache file, otherwise POP3 would open all
229
     mail files and read the metadata. same for GUIDs if they're
230
     used. */
231
0
  switch (field) {
232
0
  case MAIL_FETCH_UIDL_BACKEND:
233
0
    if (!index_pop3_uidl_can_exist(_mail)) {
234
0
      *value_r = "";
235
0
      return 0;
236
0
    }
237
0
    ret = dbox_get_cached_metadata(mail, DBOX_METADATA_POP3_UIDL,
238
0
                 MAIL_CACHE_POP3_UIDL, value_r);
239
0
    if (ret == 0) {
240
0
      index_pop3_uidl_update_exists(&mail->imail.mail.mail,
241
0
                  (*value_r)[0] != '\0');
242
0
    }
243
0
    return ret;
244
0
  case MAIL_FETCH_POP3_ORDER:
245
0
    if (!index_pop3_uidl_can_exist(_mail)) {
246
      /* we're assuming that if there's a POP3 order, there's
247
         also a UIDL */
248
0
      *value_r = "";
249
0
      return 0;
250
0
    }
251
0
    return dbox_get_cached_metadata(mail, DBOX_METADATA_POP3_ORDER,
252
0
            MAIL_CACHE_POP3_ORDER, value_r);
253
0
  case MAIL_FETCH_GUID:
254
0
    return dbox_get_cached_metadata(mail, DBOX_METADATA_GUID,
255
0
            MAIL_CACHE_GUID, value_r);
256
0
  default:
257
0
    break;
258
0
  }
259
260
0
  return index_mail_get_special(_mail, field, value_r);
261
0
}
262
263
static int
264
get_mail_stream(struct dbox_mail *mail, uoff_t offset,
265
    struct istream **stream_r)
266
0
{
267
0
  struct mail_private *pmail = &mail->imail.mail;
268
0
  struct dbox_file *file = mail->open_file;
269
0
  int ret;
270
271
0
  if ((ret = dbox_file_seek(file, offset)) <= 0) {
272
0
    *stream_r = NULL;
273
0
    return ret;
274
0
  }
275
276
0
  *stream_r = i_stream_create_limit(file->input, file->cur_physical_size);
277
0
  if (pmail->v.istream_opened != NULL) {
278
0
    if (pmail->v.istream_opened(&pmail->mail, stream_r) < 0)
279
0
      return -1;
280
0
  }
281
0
  return dbox_attachment_file_get_stream(file, stream_r);
282
0
}
283
284
int dbox_mail_get_stream(struct mail *_mail, bool get_body ATTR_UNUSED,
285
       struct message_size *hdr_size,
286
       struct message_size *body_size,
287
       struct istream **stream_r)
288
0
{
289
0
  struct dbox_storage *storage = DBOX_STORAGE(_mail->box->storage);
290
0
  struct dbox_mail *mail = DBOX_MAIL(_mail);
291
0
  struct index_mail_data *data = &mail->imail.data;
292
0
  struct istream *input;
293
0
  uoff_t offset;
294
0
  int ret;
295
296
0
  if (data->stream == NULL) {
297
0
    if (storage->v.mail_open(mail, &offset, &mail->open_file) < 0)
298
0
      return -1;
299
300
0
    ret = get_mail_stream(mail, offset, &input);
301
0
    if (ret <= 0) {
302
0
      i_stream_unref(&input);
303
0
      return -1;
304
0
    }
305
0
    data->stream = input;
306
0
    index_mail_set_read_buffer_size(_mail, input);
307
0
  }
308
309
0
  return index_mail_init_stream(&mail->imail, hdr_size, body_size,
310
0
              stream_r);
311
0
}