Coverage Report

Created: 2026-10-28 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib-index/mail-index-view.c
Line
Count
Source
1
/* Copyright (c) Dovecot authors, see top-level COPYING file */
2
3
#include "lib.h"
4
#include "array.h"
5
#include "buffer.h"
6
#include "llist.h"
7
#include "mail-index-view-private.h"
8
#include "mail-transaction-log.h"
9
10
#undef mail_index_view_clone
11
#undef mail_index_view_dup_private
12
13
struct mail_index_view *
14
mail_index_view_dup_private(const struct mail_index_view *src,
15
          const char *source_filename,
16
          unsigned int source_linenum)
17
0
{
18
0
  struct mail_index_view *view;
19
0
  struct mail_index_map *map;
20
21
0
  view = i_new(struct mail_index_view, 1);
22
0
  mail_index_view_clone(view, src, source_filename, source_linenum);
23
24
0
  map = mail_index_map_clone(view->map);
25
0
  mail_index_unmap(&view->map);
26
0
  view->map = map;
27
0
  return view;
28
0
}
29
30
void mail_index_view_clone(struct mail_index_view *dest,
31
         const struct mail_index_view *src,
32
         const char *source_filename,
33
         unsigned int source_linenum)
34
0
{
35
0
  i_zero(dest);
36
0
  dest->refcount = 1;
37
0
  dest->v = src->v;
38
0
  dest->index = src->index;
39
0
  if (src->log_view != NULL) {
40
0
    dest->log_view =
41
0
      mail_transaction_log_view_open(src->index->log);
42
0
  }
43
44
0
  dest->indexid = src->indexid;
45
0
  dest->inconsistency_id = src->inconsistency_id;
46
0
  dest->map = src->map;
47
0
  if (dest->map != NULL)
48
0
    dest->map->refcount++;
49
50
0
  dest->log_file_expunge_seq = src->log_file_expunge_seq;
51
0
  dest->log_file_expunge_offset = src->log_file_expunge_offset;
52
0
  dest->log_file_head_seq = src->log_file_head_seq;
53
0
  dest->log_file_head_offset = src->log_file_head_offset;
54
55
0
  i_array_init(&dest->module_contexts,
56
0
         I_MIN(5, mail_index_module_register.id));
57
58
0
  dest->source_filename = source_filename;
59
0
  dest->source_linenum = source_linenum;
60
61
0
  DLLIST_PREPEND(&dest->index->views, dest);
62
0
}
63
64
void mail_index_view_ref(struct mail_index_view *view)
65
0
{
66
0
  view->refcount++;
67
0
}
68
69
static void view_close(struct mail_index_view *view)
70
0
{
71
0
  i_assert(view->refcount == 0);
72
0
  i_assert(view->index->views != NULL);
73
74
0
  DLLIST_REMOVE(&view->index->views, view);
75
76
0
  mail_transaction_log_view_close(&view->log_view);
77
78
0
  if (array_is_created(&view->syncs_hidden))
79
0
    array_free(&view->syncs_hidden);
80
0
  mail_index_unmap(&view->map);
81
0
  if (array_is_created(&view->map_refs)) {
82
0
    mail_index_view_unref_maps(view);
83
0
    array_free(&view->map_refs);
84
0
  }
85
0
  array_free(&view->module_contexts);
86
0
  i_free(view);
87
0
}
88
89
bool mail_index_view_is_inconsistent(struct mail_index_view *view)
90
0
{
91
0
  if (view->index->indexid != view->indexid ||
92
0
      view->index->inconsistency_id != view->inconsistency_id)
93
0
    view->inconsistent = TRUE;
94
0
  return view->inconsistent;
95
0
}
96
97
struct mail_index *mail_index_view_get_index(struct mail_index_view *view)
98
0
{
99
0
  return view->index;
100
0
}
101
102
bool mail_index_view_have_transactions(struct mail_index_view *view)
103
0
{
104
0
  return view->transactions_list != NULL;
105
0
}
106
107
static void mail_index_view_ref_map(struct mail_index_view *view,
108
            struct mail_index_map *map)
109
0
{
110
0
  struct mail_index_map *const *maps;
111
0
  unsigned int i, count;
112
113
0
  if (array_is_created(&view->map_refs)) {
114
0
    maps = array_get(&view->map_refs, &count);
115
116
    /* if map is already referenced, do nothing */
117
0
    for (i = 0; i < count; i++) {
118
0
      if (maps[i] == map)
119
0
        return;
120
0
    }
121
0
  } else {
122
0
    i_array_init(&view->map_refs, 4);
123
0
  }
124
125
  /* reference the given mapping. the reference is dropped when the view
126
     is synchronized or closed. */
127
0
  map->refcount++;
128
0
  array_push_back(&view->map_refs, &map);
129
0
}
130
131
void mail_index_view_unref_maps(struct mail_index_view *view)
132
0
{
133
0
  struct mail_index_map **maps;
134
0
  unsigned int i, count;
135
136
0
  if (!array_is_created(&view->map_refs))
137
0
    return;
138
139
0
  maps = array_get_modifiable(&view->map_refs, &count);
140
0
  for (i = 0; i < count; i++)
141
0
    mail_index_unmap(&maps[i]);
142
143
0
  array_clear(&view->map_refs);
144
0
}
145
146
static uint32_t view_get_messages_count(struct mail_index_view *view)
147
0
{
148
0
  return view->map->hdr.messages_count;
149
0
}
150
151
static const struct mail_index_header *
152
view_get_header(struct mail_index_view *view)
153
0
{
154
0
  return &view->map->hdr;
155
0
}
156
157
static const struct mail_index_record *
158
view_lookup_full(struct mail_index_view *view, uint32_t seq,
159
     struct mail_index_map **map_r, bool *expunged_r)
160
0
{
161
0
  static struct mail_index_record broken_rec;
162
0
  struct mail_index_map *map;
163
0
  const struct mail_index_record *rec, *head_rec;
164
165
0
  i_assert(seq > 0 && seq <= mail_index_view_get_messages_count(view));
166
167
  /* look up the record */
168
0
  rec = MAIL_INDEX_REC_AT_SEQ(view->map, seq);
169
0
  if (unlikely(rec->uid == 0)) {
170
0
    if (!view->inconsistent) {
171
0
      mail_index_set_error(view->index,
172
0
        "Corrupted Index file %s: Record [%u].uid=0",
173
0
        view->index->filepath, seq);
174
0
      (void)mail_index_fsck(view->index);
175
0
      view->inconsistent = TRUE;
176
0
    }
177
178
    /* we'll need to return something so the caller doesn't crash */
179
0
    *map_r = view->map;
180
0
    if (expunged_r != NULL)
181
0
      *expunged_r = TRUE;
182
0
    return &broken_rec;
183
0
  }
184
0
  if (view->map == view->index->map) {
185
    /* view's mapping is latest. we can use it directly. */
186
0
    *map_r = view->map;
187
0
    if (expunged_r != NULL)
188
0
      *expunged_r = FALSE;
189
0
    return rec;
190
0
  }
191
192
  /* look up the record from head mapping. it may contain some changes.
193
194
     start looking up from the same sequence as in the old view.
195
     if there are no expunges, it's there. otherwise it's somewhere
196
     before (since records can't be inserted).
197
198
     usually there are only a few expunges, so just going downwards from
199
     our initial sequence position is probably faster than binary
200
     search. */
201
0
  if (seq > view->index->map->hdr.messages_count)
202
0
    seq = view->index->map->hdr.messages_count;
203
0
  if (seq == 0) {
204
    /* everything is expunged from head. use the old record. */
205
0
    *map_r = view->map;
206
0
    if (expunged_r != NULL)
207
0
      *expunged_r = TRUE;
208
0
    return rec;
209
0
  }
210
211
0
  map = view->index->map;
212
0
  do {
213
0
    head_rec = MAIL_INDEX_REC_AT_SEQ(map, seq);
214
0
    if (head_rec->uid <= rec->uid)
215
0
      break;
216
0
  } while (--seq > 0);
217
218
0
  if (head_rec->uid == rec->uid) {
219
    /* found it. use it. reference the index mapping so that the
220
       returned record doesn't get invalidated after next sync. */
221
0
    mail_index_view_ref_map(view, view->index->map);
222
0
    *map_r = view->index->map;
223
0
    if (expunged_r != NULL)
224
0
      *expunged_r = FALSE;
225
0
    return head_rec;
226
0
  } else {
227
    /* expunged from head. use the old record. */
228
0
    *map_r = view->map;
229
0
    if (expunged_r != NULL)
230
0
      *expunged_r = TRUE;
231
0
    return rec;
232
0
  }
233
0
}
234
235
static void view_lookup_uid(struct mail_index_view *view, uint32_t seq,
236
          uint32_t *uid_r)
237
0
{
238
0
  i_assert(seq > 0 && seq <= mail_index_view_get_messages_count(view));
239
240
0
  *uid_r = MAIL_INDEX_REC_AT_SEQ(view->map, seq)->uid;
241
0
}
242
243
static void view_lookup_seq_range(struct mail_index_view *view,
244
          uint32_t first_uid, uint32_t last_uid,
245
          uint32_t *first_seq_r, uint32_t *last_seq_r)
246
0
{
247
0
  mail_index_map_lookup_seq_range(view->map, first_uid, last_uid,
248
0
          first_seq_r, last_seq_r);
249
0
}
250
251
static void view_lookup_first(struct mail_index_view *view,
252
            enum mail_flags flags, uint8_t flags_mask,
253
            uint32_t *seq_r)
254
0
{
255
0
#define LOW_UPDATE(x) \
256
0
  STMT_START { if ((x) > low_uid) low_uid = x; } STMT_END
257
0
  const struct mail_index_header *hdr = &view->map->hdr;
258
0
  const struct mail_index_record *rec;
259
0
  uint32_t seq, seq2, low_uid = 1;
260
261
0
  *seq_r = 0;
262
263
0
  if ((flags_mask & MAIL_SEEN) != 0 && (flags & MAIL_SEEN) == 0)
264
0
    LOW_UPDATE(hdr->first_unseen_uid_lowwater);
265
0
  if ((flags_mask & MAIL_DELETED) != 0 && (flags & MAIL_DELETED) != 0)
266
0
    LOW_UPDATE(hdr->first_deleted_uid_lowwater);
267
268
0
  if (low_uid == 1)
269
0
    seq = 1;
270
0
  else {
271
0
    if (!mail_index_lookup_seq_range(view, low_uid, hdr->next_uid,
272
0
             &seq, &seq2))
273
0
      return;
274
0
  }
275
276
0
  i_assert(hdr->messages_count <= view->map->rec_map->records_count);
277
0
  for (; seq <= hdr->messages_count; seq++) {
278
0
    rec = MAIL_INDEX_REC_AT_SEQ(view->map, seq);
279
0
    if ((rec->flags & flags_mask) == (uint8_t)flags) {
280
0
      *seq_r = seq;
281
0
      break;
282
0
    }
283
0
  }
284
0
}
285
286
static void
287
mail_index_data_lookup_keywords(struct mail_index_map *map,
288
        const unsigned char *data,
289
        ARRAY_TYPE(keyword_indexes) *keyword_idx)
290
0
{
291
0
  const unsigned int *keyword_idx_map;
292
0
  unsigned int i, j, keyword_count, index_idx;
293
0
  uint32_t idx, hdr_size;
294
0
  uint16_t record_size, record_align;
295
296
0
  array_clear(keyword_idx);
297
0
  if (data == NULL) {
298
    /* no keywords at all in index */
299
0
    return;
300
0
  }
301
0
  (void)mail_index_ext_get_size(map, map->index->keywords_ext_id,
302
0
              &hdr_size, &record_size,
303
0
              &record_align);
304
305
  /* keyword_idx_map[] contains file => index keyword mapping */
306
0
  if (!array_is_created(&map->keyword_idx_map))
307
0
    return;
308
309
0
  keyword_idx_map = array_get(&map->keyword_idx_map, &keyword_count);
310
0
  for (i = 0; i < record_size; i++) {
311
    /* first do the quick check to see if there's keywords at all */
312
0
    if (data[i] == 0)
313
0
      continue;
314
315
0
    idx = i * CHAR_BIT;
316
0
    for (j = 0; j < CHAR_BIT; j++, idx++) {
317
0
      if ((data[i] & (1 << j)) == 0)
318
0
        continue;
319
320
0
      if (idx >= keyword_count) {
321
        /* extra bits set in keyword bytes.
322
           shouldn't happen, but just ignore. */
323
0
        break;
324
0
      }
325
326
0
      index_idx = keyword_idx_map[idx];
327
0
      array_push_back(keyword_idx, &index_idx);
328
0
    }
329
0
  }
330
0
}
331
332
static void view_lookup_keywords(struct mail_index_view *view, uint32_t seq,
333
         ARRAY_TYPE(keyword_indexes) *keyword_idx)
334
0
{
335
0
  struct mail_index_map *map;
336
0
  const void *data;
337
338
0
  mail_index_lookup_ext_full(view, seq, view->index->keywords_ext_id,
339
0
           &map, &data, NULL);
340
0
  mail_index_data_lookup_keywords(map, data, keyword_idx);
341
0
}
342
343
static const void *
344
view_map_lookup_ext_full(struct mail_index_map *map,
345
       const struct mail_index_record *rec, uint32_t ext_id)
346
0
{
347
0
  const struct mail_index_ext *ext;
348
0
  uint32_t idx;
349
350
0
  if (!mail_index_map_get_ext_idx(map, ext_id, &idx))
351
0
    return NULL;
352
353
0
  ext = array_idx(&map->extensions, idx);
354
0
  return ext->record_offset == 0 ? NULL :
355
0
    CONST_PTR_OFFSET(rec, ext->record_offset);
356
0
}
357
358
static void
359
view_lookup_ext_full(struct mail_index_view *view, uint32_t seq,
360
         uint32_t ext_id, struct mail_index_map **map_r,
361
         const void **data_r, bool *expunged_r)
362
0
{
363
0
  const struct mail_index_record *rec;
364
365
0
  rec = view->v.lookup_full(view, seq, map_r, expunged_r);
366
0
  *data_r = view_map_lookup_ext_full(*map_r, rec, ext_id);
367
0
}
368
369
static void view_get_header_ext(struct mail_index_view *view,
370
        struct mail_index_map *map, uint32_t ext_id,
371
        const void **data_r, size_t *data_size_r)
372
0
{
373
0
  const struct mail_index_ext *ext;
374
0
  uint32_t idx;
375
376
0
  if (map == NULL) {
377
    /* no mapping given, use head mapping */
378
0
    map = view->index->map;
379
0
  }
380
381
0
  if (!mail_index_map_get_ext_idx(map, ext_id, &idx)) {
382
    /* extension doesn't exist in this index file */
383
0
    *data_r = NULL;
384
0
    *data_size_r = 0;
385
0
    return;
386
0
  }
387
388
0
  ext = array_idx(&map->extensions, idx);
389
0
  *data_r = MAIL_INDEX_MAP_HDR_OFFSET(map, ext->hdr_offset);
390
0
  *data_size_r = ext->hdr_size;
391
0
}
392
393
static bool view_ext_get_reset_id(struct mail_index_view *view ATTR_UNUSED,
394
          struct mail_index_map *map,
395
          uint32_t ext_id, uint32_t *reset_id_r)
396
0
{
397
0
  const struct mail_index_ext *ext;
398
0
  uint32_t idx;
399
400
0
  if (!mail_index_map_get_ext_idx(map, ext_id, &idx))
401
0
    return FALSE;
402
403
0
  ext = array_idx(&map->extensions, idx);
404
0
  *reset_id_r = ext->reset_id;
405
0
  return TRUE;
406
0
}
407
408
void mail_index_view_close(struct mail_index_view **_view)
409
0
{
410
0
  struct mail_index_view *view = *_view;
411
412
0
  if (view == NULL)
413
0
    return;
414
415
0
  *_view = NULL;
416
0
  if (--view->refcount > 0)
417
0
    return;
418
419
0
  i_assert(view->transactions_list == NULL);
420
421
0
  view->v.close(view);
422
0
}
423
424
uint32_t mail_index_view_get_messages_count(struct mail_index_view *view)
425
0
{
426
0
  return view->v.get_messages_count(view);
427
0
}
428
429
const struct mail_index_header *
430
mail_index_get_header(struct mail_index_view *view)
431
0
{
432
0
  return view->v.get_header(view);
433
0
}
434
435
const struct mail_index_record *
436
mail_index_lookup(struct mail_index_view *view, uint32_t seq)
437
0
{
438
0
  struct mail_index_map *map;
439
440
0
  return mail_index_lookup_full(view, seq, &map, NULL);
441
0
}
442
443
const struct mail_index_record *
444
mail_index_lookup_full(struct mail_index_view *view, uint32_t seq,
445
           struct mail_index_map **map_r, bool *expunged_r)
446
0
{
447
0
  return view->v.lookup_full(view, seq, map_r, expunged_r);
448
0
}
449
450
bool mail_index_is_expunged(struct mail_index_view *view, uint32_t seq)
451
0
{
452
0
  struct mail_index_map *map;
453
0
  bool expunged;
454
455
0
  (void)view->v.lookup_full(view, seq, &map, &expunged);
456
0
  return expunged;
457
0
}
458
459
void mail_index_map_lookup_keywords(struct mail_index_map *map, uint32_t seq,
460
            ARRAY_TYPE(keyword_indexes) *keyword_idx)
461
0
{
462
0
  const struct mail_index_ext *ext;
463
0
  const struct mail_index_record *rec;
464
0
  const void *data;
465
0
  uint32_t idx;
466
467
0
  if (!mail_index_map_get_ext_idx(map, map->index->keywords_ext_id, &idx))
468
0
    data = NULL;
469
0
  else {
470
0
    rec = MAIL_INDEX_REC_AT_SEQ(map, seq);
471
0
    ext = array_idx(&map->extensions, idx);
472
0
    data = ext->record_offset == 0 ? NULL :
473
0
      CONST_PTR_OFFSET(rec, ext->record_offset);
474
0
  }
475
0
  mail_index_data_lookup_keywords(map, data, keyword_idx);
476
0
}
477
478
void mail_index_lookup_keywords(struct mail_index_view *view, uint32_t seq,
479
        ARRAY_TYPE(keyword_indexes) *keyword_idx)
480
0
{
481
0
  view->v.lookup_keywords(view, seq, keyword_idx);
482
0
}
483
484
void mail_index_lookup_view_flags(struct mail_index_view *view, uint32_t seq,
485
          enum mail_flags *flags_r,
486
          ARRAY_TYPE(keyword_indexes) *keyword_idx)
487
0
{
488
0
  const struct mail_index_record *rec;
489
0
  const unsigned char *keyword_data;
490
491
0
  i_assert(seq > 0 && seq <= mail_index_view_get_messages_count(view));
492
493
0
  rec = MAIL_INDEX_REC_AT_SEQ(view->map, seq);
494
0
  *flags_r = rec->flags;
495
496
0
  keyword_data = view_map_lookup_ext_full(view->map, rec,
497
0
            view->index->keywords_ext_id);
498
0
  mail_index_data_lookup_keywords(view->map, keyword_data, keyword_idx);
499
0
}
500
501
void mail_index_lookup_uid(struct mail_index_view *view, uint32_t seq,
502
         uint32_t *uid_r)
503
0
{
504
0
  view->v.lookup_uid(view, seq, uid_r);
505
0
}
506
507
bool mail_index_lookup_seq_range(struct mail_index_view *view,
508
         uint32_t first_uid, uint32_t last_uid,
509
         uint32_t *first_seq_r, uint32_t *last_seq_r)
510
0
{
511
0
  view->v.lookup_seq_range(view, first_uid, last_uid,
512
0
         first_seq_r, last_seq_r);
513
0
  return *first_seq_r != 0;
514
0
}
515
516
bool mail_index_lookup_seq(struct mail_index_view *view,
517
         uint32_t uid, uint32_t *seq_r)
518
0
{
519
0
  view->v.lookup_seq_range(view, uid, uid, seq_r, seq_r);
520
0
  return *seq_r != 0;
521
0
}
522
523
void mail_index_lookup_first(struct mail_index_view *view,
524
           enum mail_flags flags, uint8_t flags_mask,
525
           uint32_t *seq_r)
526
0
{
527
0
  view->v.lookup_first(view, flags, flags_mask, seq_r);
528
0
}
529
530
void mail_index_lookup_ext(struct mail_index_view *view, uint32_t seq,
531
         uint32_t ext_id, const void **data_r,
532
         bool *expunged_r)
533
0
{
534
0
  struct mail_index_map *map;
535
536
0
  mail_index_lookup_ext_full(view, seq, ext_id, &map, data_r, expunged_r);
537
0
}
538
539
void mail_index_lookup_ext_full(struct mail_index_view *view, uint32_t seq,
540
        uint32_t ext_id, struct mail_index_map **map_r,
541
        const void **data_r, bool *expunged_r)
542
0
{
543
0
  view->v.lookup_ext_full(view, seq, ext_id, map_r, data_r, expunged_r);
544
0
}
545
546
void mail_index_get_header_ext(struct mail_index_view *view, uint32_t ext_id,
547
             const void **data_r, size_t *data_size_r)
548
0
{
549
0
  view->v.get_header_ext(view, NULL, ext_id, data_r, data_size_r);
550
0
}
551
552
void mail_index_map_get_header_ext(struct mail_index_view *view,
553
           struct mail_index_map *map, uint32_t ext_id,
554
           const void **data_r, size_t *data_size_r)
555
0
{
556
0
  view->v.get_header_ext(view, map, ext_id, data_r, data_size_r);
557
0
}
558
559
bool mail_index_ext_get_reset_id(struct mail_index_view *view,
560
         struct mail_index_map *map,
561
         uint32_t ext_id, uint32_t *reset_id_r)
562
0
{
563
0
  return view->v.ext_get_reset_id(view, map, ext_id, reset_id_r);
564
0
}
565
566
void mail_index_ext_get_size(struct mail_index_map *map, uint32_t ext_id,
567
           uint32_t *hdr_size_r, uint16_t *record_size_r,
568
           uint16_t *record_align_r)
569
0
{
570
0
  const struct mail_index_ext *ext;
571
0
  uint32_t idx;
572
573
0
  i_assert(map != NULL);
574
575
0
  if (!mail_index_map_get_ext_idx(map, ext_id, &idx)) {
576
    /* extension doesn't exist in this index file */
577
0
    *hdr_size_r = 0;
578
0
    *record_size_r = 0;
579
0
    *record_align_r = 0;
580
0
    return;
581
0
  }
582
583
0
  ext = array_idx(&map->extensions, idx);
584
0
  *hdr_size_r = ext->hdr_size;
585
0
  *record_size_r = ext->record_size;
586
0
  *record_align_r = ext->record_align;
587
0
}
588
589
static struct mail_index_view_vfuncs view_vfuncs = {
590
  view_close,
591
  view_get_messages_count,
592
  view_get_header,
593
  view_lookup_full,
594
  view_lookup_uid,
595
  view_lookup_seq_range,
596
  view_lookup_first,
597
  view_lookup_keywords,
598
  view_lookup_ext_full,
599
  view_get_header_ext,
600
  view_ext_get_reset_id
601
};
602
603
struct mail_index_view *
604
mail_index_view_open_with_map(struct mail_index *index,
605
            struct mail_index_map *map)
606
0
{
607
0
  struct mail_index_view *view;
608
609
0
  view = i_new(struct mail_index_view, 1);
610
0
  view->refcount = 1;
611
0
  view->v = view_vfuncs;
612
0
  view->index = index;
613
0
  view->log_view = mail_transaction_log_view_open(index->log);
614
615
0
  view->indexid = index->indexid;
616
0
  view->inconsistency_id = index->inconsistency_id;
617
0
  view->map = map;
618
0
  view->map->refcount++;
619
620
0
  view->log_file_expunge_seq = view->log_file_head_seq =
621
0
    view->map->hdr.log_file_seq;
622
0
  view->log_file_expunge_offset = view->log_file_head_offset =
623
0
    view->map->hdr.log_file_head_offset;
624
625
0
  i_array_init(&view->module_contexts,
626
0
         I_MIN(5, mail_index_module_register.id));
627
0
  DLLIST_PREPEND(&index->views, view);
628
0
  return view;
629
0
}
630
631
#undef mail_index_view_open
632
struct mail_index_view *
633
mail_index_view_open(struct mail_index *index,
634
         const char *source_filename, unsigned int source_linenum)
635
0
{
636
0
  struct mail_index_view *view;
637
638
0
  view = mail_index_view_open_with_map(index, index->map);
639
  /* these can be used to debug mail_index_view_close() leaks */
640
0
  view->source_filename = source_filename;
641
0
  view->source_linenum = source_linenum;
642
0
  return view;
643
0
}
644
645
const struct mail_index_ext *
646
mail_index_view_get_ext(struct mail_index_view *view, uint32_t ext_id)
647
0
{
648
0
  uint32_t idx;
649
650
0
  if (!mail_index_map_get_ext_idx(view->map, ext_id, &idx))
651
0
    return NULL;
652
653
0
  return array_idx(&view->map->extensions, idx);
654
0
}