/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 | } |