Coverage Report

Created: 2026-09-01 06:14

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib-mail/message-header-decode.c
Line
Count
Source
1
/* Copyright (c) Dovecot authors, see top-level COPYING file */
2
3
#include "lib.h"
4
#include "base64.h"
5
#include "buffer.h"
6
#include "unichar.h"
7
#include "charset-utf8.h"
8
#include "quoted-printable.h"
9
#include "message-header-decode.h"
10
11
static size_t
12
message_header_decode_encoded(const unsigned char *data, size_t size,
13
            buffer_t *decodebuf, size_t *charsetlen_r)
14
121k
{
15
360k
#define QCOUNT 3
16
121k
  unsigned int num = 0;
17
121k
  size_t i, start_pos[QCOUNT] = {0, 0, 0};
18
19
  /* data should contain "charset?encoding?text?=" */
20
47.7M
  for (i = 0; i < size; i++) {
21
47.7M
    if (data[i] == '?') {
22
360k
      start_pos[num++] = i;
23
360k
      if (num == QCOUNT)
24
119k
        break;
25
360k
    }
26
47.7M
  }
27
28
121k
  if (i+1 >= size || data[i+1] != '=') {
29
    /* invalid block */
30
44.0k
    return 0;
31
44.0k
  }
32
33
121k
  i_assert(num == QCOUNT);
34
35
77.3k
  buffer_append(decodebuf, data, start_pos[0]);
36
77.3k
  buffer_append_c(decodebuf, '\0');
37
77.3k
  *charsetlen_r = decodebuf->used;
38
39
77.3k
  switch (data[start_pos[0]+1]) {
40
2.18k
  case 'q':
41
2.66k
  case 'Q':
42
2.66k
    if (quoted_printable_q_decode(data + start_pos[1] + 1,
43
2.66k
                start_pos[2] - start_pos[1] - 1,
44
2.66k
                decodebuf) < 0) {
45
      /* we skipped over some invalid data */
46
254
    }
47
2.66k
    break;
48
71.6k
  case 'b':
49
72.5k
  case 'B':
50
72.5k
    if (base64_decode(data + start_pos[1] + 1,
51
72.5k
          start_pos[2] - start_pos[1] - 1,
52
72.5k
          decodebuf) < 0) {
53
      /* contains invalid data. show what we got so far. */
54
71.1k
    }
55
72.5k
    break;
56
2.17k
  default:
57
    /* unknown encoding */
58
2.17k
    return 0;
59
77.3k
  }
60
61
75.1k
  return start_pos[2] + 2;
62
77.3k
}
63
64
static bool is_only_lwsp(const unsigned char *data, size_t size)
65
109k
{
66
109k
  size_t i;
67
68
110k
  for (i = 0; i < size; i++) {
69
110k
    if (!(data[i] == ' ' || data[i] == '\t' ||
70
110k
          data[i] == '\r' || data[i] == '\n'))
71
109k
      return FALSE;
72
110k
  }
73
244
  return TRUE;
74
109k
}
75
76
void message_header_decode(const unsigned char *data, size_t size,
77
         message_header_decode_callback_t *callback,
78
         void *context)
79
215k
{
80
215k
  buffer_t *decodebuf = NULL;
81
215k
  size_t charsetlen = 0;
82
215k
  size_t pos, start_pos, ret;
83
84
  /* =?charset?Q|B?text?= */
85
215k
  start_pos = 0;
86
115M
  for (pos = 0; pos + 1 < size; ) {
87
115M
    if (data[pos] != '=' || data[pos+1] != '?') {
88
115M
      pos++;
89
115M
      continue;
90
115M
    }
91
92
    /* encoded string beginning */
93
121k
    if (pos != start_pos &&
94
109k
        !is_only_lwsp(data+start_pos, pos-start_pos)) {
95
      /* send the unencoded data so far */
96
109k
      if (!callback(data + start_pos, pos - start_pos,
97
109k
              NULL, context)) {
98
0
        start_pos = size;
99
0
        break;
100
0
      }
101
109k
    }
102
103
121k
    if (decodebuf == NULL) {
104
2.76k
      decodebuf = buffer_create_dynamic(default_pool,
105
2.76k
                size - pos);
106
118k
    } else {
107
118k
      buffer_set_used_size(decodebuf, 0);
108
118k
    }
109
110
121k
    pos += 2;
111
121k
    ret = message_header_decode_encoded(data + pos, size - pos,
112
121k
                decodebuf, &charsetlen);
113
121k
    if (ret == 0) {
114
46.2k
      start_pos = pos-2;
115
46.2k
      continue;
116
46.2k
    }
117
75.1k
    pos += ret;
118
119
75.1k
    if (decodebuf->used > charsetlen) {
120
      /* decodebuf contains <charset> NUL <text> */
121
65.7k
      if (!callback(CONST_PTR_OFFSET(decodebuf->data,
122
65.7k
                   charsetlen),
123
65.7k
              decodebuf->used - charsetlen,
124
65.7k
              decodebuf->data, context)) {
125
0
        start_pos = size;
126
0
        break;
127
0
      }
128
65.7k
    }
129
130
75.1k
    start_pos = pos;
131
75.1k
  }
132
133
215k
  if (size != start_pos) {
134
179k
    i_assert(size > start_pos);
135
179k
    (void)callback(data + start_pos, size - start_pos,
136
179k
             NULL, context);
137
179k
  }
138
215k
  buffer_free(&decodebuf);
139
215k
}
140
141
struct decode_utf8_context {
142
  buffer_t *dest;
143
  normalizer_func_t *normalizer;
144
  bool changed:1;
145
};
146
147
static bool
148
decode_utf8_callback(const unsigned char *data, size_t size,
149
         const char *charset, void *context)
150
354k
{
151
354k
  struct decode_utf8_context *ctx = context;
152
354k
  struct charset_translation *t;
153
154
354k
  if (charset == NULL || charset_is_utf8(charset)) {
155
    /* ASCII / UTF-8 */
156
288k
    if (ctx->normalizer != NULL) {
157
288k
      (void)ctx->normalizer(data, size, ctx->dest);
158
288k
    } else {
159
0
      if (uni_utf8_get_valid_data(data, size, ctx->dest))
160
0
        buffer_append(ctx->dest, data, size);
161
0
    }
162
288k
    return TRUE;
163
288k
  }
164
165
65.3k
  if (charset_to_utf8_begin(charset, ctx->normalizer, &t) < 0) {
166
    /* data probably still contains some valid ASCII characters.
167
       append them. */
168
3.47k
    if (uni_utf8_get_valid_data(data, size, ctx->dest))
169
435
      buffer_append(ctx->dest, data, size);
170
3.47k
    return TRUE;
171
3.47k
  }
172
173
  /* ignore any errors */
174
61.8k
  (void)charset_to_utf8(t, data, &size, ctx->dest);
175
61.8k
  charset_to_utf8_end(&t);
176
61.8k
  return TRUE;
177
65.3k
}
178
179
void message_header_decode_utf8(const unsigned char *data, size_t size,
180
        buffer_t *dest, normalizer_func_t *normalizer)
181
215k
{
182
215k
  struct decode_utf8_context ctx;
183
184
215k
  i_zero(&ctx);
185
215k
  ctx.dest = dest;
186
215k
  ctx.normalizer = normalizer;
187
215k
  message_header_decode(data, size, decode_utf8_callback, &ctx);
188
215k
}