Coverage Report

Created: 2026-10-17 06:21

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
170k
{
15
507k
#define QCOUNT 3
16
170k
  unsigned int num = 0;
17
170k
  size_t i, start_pos[QCOUNT] = {0, 0, 0};
18
19
  /* data should contain "charset?encoding?text?=" */
20
53.0M
  for (i = 0; i < size; i++) {
21
53.0M
    if (data[i] == '?') {
22
507k
      start_pos[num++] = i;
23
507k
      if (num == QCOUNT)
24
168k
        break;
25
507k
    }
26
53.0M
  }
27
28
170k
  if (i+1 >= size || data[i+1] != '=') {
29
    /* invalid block */
30
58.7k
    return 0;
31
58.7k
  }
32
33
170k
  i_assert(num == QCOUNT);
34
35
112k
  buffer_append(decodebuf, data, start_pos[0]);
36
112k
  buffer_append_c(decodebuf, '\0');
37
112k
  *charsetlen_r = decodebuf->used;
38
39
112k
  switch (data[start_pos[0]+1]) {
40
2.03k
  case 'q':
41
3.10k
  case 'Q':
42
3.10k
    if (quoted_printable_q_decode(data + start_pos[1] + 1,
43
3.10k
                start_pos[2] - start_pos[1] - 1,
44
3.10k
                decodebuf) < 0) {
45
      /* we skipped over some invalid data */
46
244
    }
47
3.10k
    break;
48
105k
  case 'b':
49
106k
  case 'B':
50
106k
    if (base64_decode(data + start_pos[1] + 1,
51
106k
          start_pos[2] - start_pos[1] - 1,
52
106k
          decodebuf) < 0) {
53
      /* contains invalid data. show what we got so far. */
54
105k
    }
55
106k
    break;
56
2.51k
  default:
57
    /* unknown encoding */
58
2.51k
    return 0;
59
112k
  }
60
61
109k
  return start_pos[2] + 2;
62
112k
}
63
64
static bool is_only_lwsp(const unsigned char *data, size_t size)
65
145k
{
66
145k
  size_t i;
67
68
146k
  for (i = 0; i < size; i++) {
69
146k
    if (!(data[i] == ' ' || data[i] == '\t' ||
70
145k
          data[i] == '\r' || data[i] == '\n'))
71
145k
      return FALSE;
72
146k
  }
73
261
  return TRUE;
74
145k
}
75
76
void message_header_decode(const unsigned char *data, size_t size,
77
         message_header_decode_callback_t *callback,
78
         void *context)
79
229k
{
80
229k
  buffer_t *decodebuf = NULL;
81
229k
  size_t charsetlen = 0;
82
229k
  size_t pos, start_pos, ret;
83
84
  /* =?charset?Q|B?text?= */
85
229k
  start_pos = 0;
86
122M
  for (pos = 0; pos + 1 < size; ) {
87
122M
    if (data[pos] != '=' || data[pos+1] != '?') {
88
122M
      pos++;
89
122M
      continue;
90
122M
    }
91
92
    /* encoded string beginning */
93
170k
    if (pos != start_pos &&
94
145k
        !is_only_lwsp(data+start_pos, pos-start_pos)) {
95
      /* send the unencoded data so far */
96
145k
      if (!callback(data + start_pos, pos - start_pos,
97
145k
              NULL, context)) {
98
0
        start_pos = size;
99
0
        break;
100
0
      }
101
145k
    }
102
103
170k
    if (decodebuf == NULL) {
104
3.02k
      decodebuf = buffer_create_dynamic(default_pool,
105
3.02k
                size - pos);
106
167k
    } else {
107
167k
      buffer_set_used_size(decodebuf, 0);
108
167k
    }
109
110
170k
    pos += 2;
111
170k
    ret = message_header_decode_encoded(data + pos, size - pos,
112
170k
                decodebuf, &charsetlen);
113
170k
    if (ret == 0) {
114
61.2k
      start_pos = pos-2;
115
61.2k
      continue;
116
61.2k
    }
117
109k
    pos += ret;
118
119
109k
    if (decodebuf->used > charsetlen) {
120
      /* decodebuf contains <charset> NUL <text> */
121
100k
      if (!callback(CONST_PTR_OFFSET(decodebuf->data,
122
100k
                   charsetlen),
123
100k
              decodebuf->used - charsetlen,
124
100k
              decodebuf->data, context)) {
125
0
        start_pos = size;
126
0
        break;
127
0
      }
128
100k
    }
129
130
109k
    start_pos = pos;
131
109k
  }
132
133
229k
  if (size != start_pos) {
134
189k
    i_assert(size > start_pos);
135
189k
    (void)callback(data + start_pos, size - start_pos,
136
189k
             NULL, context);
137
189k
  }
138
229k
  buffer_free(&decodebuf);
139
229k
}
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
435k
{
151
435k
  struct decode_utf8_context *ctx = context;
152
435k
  struct charset_translation *t;
153
154
435k
  if (charset == NULL || charset_is_utf8(charset)) {
155
    /* ASCII / UTF-8 */
156
335k
    if (ctx->normalizer != NULL) {
157
335k
      (void)ctx->normalizer(data, size, ctx->dest);
158
335k
    } else {
159
0
      if (uni_utf8_get_valid_data(data, size, ctx->dest))
160
0
        buffer_append(ctx->dest, data, size);
161
0
    }
162
335k
    return TRUE;
163
335k
  }
164
165
99.9k
  if (charset_to_utf8_begin(charset, ctx->normalizer, &t) < 0) {
166
    /* data probably still contains some valid ASCII characters.
167
       append them. */
168
5.84k
    if (uni_utf8_get_valid_data(data, size, ctx->dest))
169
1.14k
      buffer_append(ctx->dest, data, size);
170
5.84k
    return TRUE;
171
5.84k
  }
172
173
  /* ignore any errors */
174
94.1k
  (void)charset_to_utf8(t, data, &size, ctx->dest);
175
94.1k
  charset_to_utf8_end(&t);
176
94.1k
  return TRUE;
177
99.9k
}
178
179
void message_header_decode_utf8(const unsigned char *data, size_t size,
180
        buffer_t *dest, normalizer_func_t *normalizer)
181
229k
{
182
229k
  struct decode_utf8_context ctx;
183
184
229k
  i_zero(&ctx);
185
229k
  ctx.dest = dest;
186
229k
  ctx.normalizer = normalizer;
187
229k
  message_header_decode(data, size, decode_utf8_callback, &ctx);
188
229k
}