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