/src/dovecot/src/lib-mail/rfc822-parser.c
Line | Count | Source |
1 | | /* Copyright (c) Dovecot authors, see top-level COPYING file */ |
2 | | |
3 | | #include "lib.h" |
4 | | #include "str.h" |
5 | | #include "strescape.h" |
6 | | #include "idna.h" |
7 | | #include "idna-punycode.h" |
8 | | #include "rfc822-parser.h" |
9 | | |
10 | | /* |
11 | | atext = ALPHA / DIGIT / ; Any character except controls, |
12 | | "!" / "#" / ; SP, and specials. |
13 | | "$" / "%" / ; Used for atoms |
14 | | "&" / "'" / |
15 | | "*" / "+" / |
16 | | "-" / "/" / |
17 | | "=" / "?" / |
18 | | "^" / "_" / |
19 | | "`" / "{" / |
20 | | "|" / "}" / |
21 | | "~" |
22 | | |
23 | | MIME: |
24 | | |
25 | | token := 1*<any (US-ASCII) CHAR except SPACE, CTLs, |
26 | | or tspecials> |
27 | | tspecials := "(" / ")" / "<" / ">" / "@" / |
28 | | "," / ";" / ":" / "\" / <"> |
29 | | "/" / "[" / "]" / "?" / "=" |
30 | | |
31 | | So token is same as dot-atom, except stops also at '/', '?' and '='. |
32 | | */ |
33 | | |
34 | | /* atext chars are marked with 1, alpha and digits with 2, |
35 | | atext-but-mime-tspecials with 4 */ |
36 | | unsigned char rfc822_atext_chars[256] = { |
37 | | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0-15 */ |
38 | | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16-31 */ |
39 | | 0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 4, /* 32-47 */ |
40 | | 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 4, 0, 4, /* 48-63 */ |
41 | | 0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 64-79 */ |
42 | | 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 1, 1, /* 80-95 */ |
43 | | 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 96-111 */ |
44 | | 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 0, /* 112-127 */ |
45 | | |
46 | | 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, |
47 | | 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, |
48 | | 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, |
49 | | 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, |
50 | | 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, |
51 | | 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, |
52 | | 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, |
53 | | 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2 |
54 | | }; |
55 | | |
56 | | void rfc822_parser_init(struct rfc822_parser_context *ctx, |
57 | | const unsigned char *data, size_t size, |
58 | | string_t *last_comment) |
59 | 48.9k | { |
60 | 48.9k | i_zero(ctx); |
61 | 48.9k | ctx->data = data; |
62 | 48.9k | ctx->end = data + size; |
63 | 48.9k | ctx->last_comment = last_comment; |
64 | 48.9k | } |
65 | | |
66 | | int rfc822_skip_comment(struct rfc822_parser_context *ctx) |
67 | 10.3k | { |
68 | 10.3k | const unsigned char *start; |
69 | 10.3k | size_t len; |
70 | 10.3k | int level = 1; |
71 | | |
72 | 10.3k | i_assert(*ctx->data == '('); |
73 | | |
74 | 10.3k | if (ctx->last_comment != NULL) |
75 | 0 | str_truncate(ctx->last_comment, 0); |
76 | | |
77 | 10.3k | start = ++ctx->data; |
78 | 1.66M | for (; ctx->data < ctx->end; ctx->data++) { |
79 | 1.65M | switch (*ctx->data) { |
80 | 136k | case '\0': |
81 | 136k | if (ctx->last_comment != NULL && |
82 | 0 | ctx->nul_replacement_str != NULL) { |
83 | 0 | str_append_data(ctx->last_comment, start, |
84 | 0 | ctx->data - start); |
85 | 0 | str_append(ctx->last_comment, |
86 | 0 | ctx->nul_replacement_str); |
87 | 0 | start = ctx->data + 1; |
88 | 0 | } |
89 | 136k | break; |
90 | 1.07M | case '(': |
91 | 1.07M | level++; |
92 | 1.07M | break; |
93 | 24.0k | case ')': |
94 | 24.0k | if (--level == 0) { |
95 | 2.57k | if (ctx->last_comment != NULL) { |
96 | 0 | str_append_data(ctx->last_comment, start, |
97 | 0 | ctx->data - start); |
98 | 0 | } |
99 | 2.57k | ctx->data++; |
100 | 2.57k | return ctx->data < ctx->end ? 1 : 0; |
101 | 2.57k | } |
102 | 21.4k | break; |
103 | 21.4k | case '\n': |
104 | | /* folding whitespace, remove the (CR)LF */ |
105 | 1.42k | if (ctx->last_comment == NULL) |
106 | 1.42k | break; |
107 | 0 | len = ctx->data - start; |
108 | 0 | if (len > 0 && start[len-1] == '\r') |
109 | 0 | len--; |
110 | 0 | str_append_data(ctx->last_comment, start, len); |
111 | 0 | start = ctx->data + 1; |
112 | 0 | break; |
113 | 1.98k | case '\\': |
114 | 1.98k | ctx->data++; |
115 | 1.98k | if (ctx->data >= ctx->end) |
116 | 452 | return -1; |
117 | | |
118 | 1.53k | if (*ctx->data == '\r' || *ctx->data == '\n' || |
119 | 1.06k | *ctx->data == '\0') { |
120 | | /* quoted-pair doesn't allow CR/LF/NUL. |
121 | | They are part of the obs-qp though, so don't |
122 | | return them as error. */ |
123 | 827 | ctx->data--; |
124 | 827 | break; |
125 | 827 | } |
126 | 707 | if (ctx->last_comment != NULL) { |
127 | 0 | str_append_data(ctx->last_comment, start, |
128 | 0 | ctx->data - start - 1); |
129 | 0 | } |
130 | 707 | start = ctx->data; |
131 | 707 | break; |
132 | 1.65M | } |
133 | 1.65M | } |
134 | | |
135 | | /* missing ')' */ |
136 | 7.27k | return -1; |
137 | 10.3k | } |
138 | | |
139 | | int rfc822_skip_lwsp(struct rfc822_parser_context *ctx) |
140 | 974k | { |
141 | 1.02M | for (; ctx->data < ctx->end;) { |
142 | 1.00M | if (*ctx->data == ' ' || *ctx->data == '\t' || |
143 | 982k | *ctx->data == '\r' || *ctx->data == '\n') { |
144 | 49.4k | ctx->data++; |
145 | 49.4k | continue; |
146 | 49.4k | } |
147 | | |
148 | 952k | if (*ctx->data != '(') |
149 | 942k | break; |
150 | | |
151 | 10.3k | if (rfc822_skip_comment(ctx) < 0) |
152 | 7.73k | return -1; |
153 | 10.3k | } |
154 | 966k | return ctx->data < ctx->end ? 1 : 0; |
155 | 974k | } |
156 | | |
157 | | int rfc822_parse_atom(struct rfc822_parser_context *ctx, string_t *str) |
158 | 0 | { |
159 | 0 | const unsigned char *start; |
160 | | |
161 | | /* |
162 | | atom = [CFWS] 1*atext [CFWS] |
163 | | atext = |
164 | | ; Any character except controls, SP, and specials. |
165 | | */ |
166 | 0 | if (ctx->data >= ctx->end || !IS_ATEXT(*ctx->data)) |
167 | 0 | return -1; |
168 | | |
169 | 0 | for (start = ctx->data++; ctx->data < ctx->end; ctx->data++) { |
170 | 0 | if (IS_ATEXT(*ctx->data)) |
171 | 0 | continue; |
172 | | |
173 | 0 | str_append_data(str, start, ctx->data - start); |
174 | 0 | return rfc822_skip_lwsp(ctx); |
175 | 0 | } |
176 | | |
177 | 0 | str_append_data(str, start, ctx->data - start); |
178 | 0 | return 0; |
179 | 0 | } |
180 | | |
181 | | int rfc822_parse_dot_atom(struct rfc822_parser_context *ctx, string_t *str) |
182 | 0 | { |
183 | 0 | const unsigned char *start; |
184 | 0 | int ret; |
185 | | |
186 | | /* |
187 | | dot-atom = [CFWS] dot-atom-text [CFWS] |
188 | | dot-atom-text = 1*atext *("." 1*atext) |
189 | | |
190 | | atext = |
191 | | ; Any character except controls, SP, and specials. |
192 | | |
193 | | For RFC-822 compatibility allow LWSP around '.' |
194 | | */ |
195 | 0 | if (ctx->data >= ctx->end || !IS_ATEXT(*ctx->data)) |
196 | 0 | return -1; |
197 | | |
198 | 0 | for (start = ctx->data++; ctx->data < ctx->end; ) { |
199 | 0 | if (IS_ATEXT(*ctx->data)) { |
200 | 0 | ctx->data++; |
201 | 0 | continue; |
202 | 0 | } |
203 | | |
204 | 0 | if (start == ctx->data) |
205 | 0 | return -1; |
206 | 0 | str_append_data(str, start, ctx->data - start); |
207 | |
|
208 | 0 | if ((ret = rfc822_skip_lwsp(ctx)) <= 0) |
209 | 0 | return ret; |
210 | | |
211 | 0 | if (*ctx->data != '.') |
212 | 0 | return 1; |
213 | | |
214 | 0 | ctx->data++; |
215 | 0 | str_append_c(str, '.'); |
216 | |
|
217 | 0 | if (rfc822_skip_lwsp(ctx) <= 0) |
218 | 0 | return -1; |
219 | 0 | start = ctx->data; |
220 | 0 | } |
221 | | |
222 | 0 | i_assert(start != ctx->data); |
223 | 0 | str_append_data(str, start, ctx->data - start); |
224 | 0 | return 0; |
225 | 0 | } |
226 | | |
227 | | int rfc822_parse_mime_token(struct rfc822_parser_context *ctx, string_t *str) |
228 | 444k | { |
229 | 444k | const unsigned char *start; |
230 | | |
231 | 19.5M | for (start = ctx->data; ctx->data < ctx->end; ctx->data++) { |
232 | 19.5M | if (IS_ATEXT_NON_TSPECIAL(*ctx->data) || *ctx->data == '.') |
233 | 19.1M | continue; |
234 | | |
235 | 437k | str_append_data(str, start, ctx->data - start); |
236 | 437k | return rfc822_skip_lwsp(ctx); |
237 | 19.5M | } |
238 | | |
239 | 7.43k | str_append_data(str, start, ctx->data - start); |
240 | 7.43k | return 0; |
241 | 444k | } |
242 | | |
243 | | int rfc822_parse_quoted_string(struct rfc822_parser_context *ctx, string_t *str) |
244 | 8.35k | { |
245 | 8.35k | const unsigned char *start; |
246 | 8.35k | size_t len; |
247 | | |
248 | 8.35k | i_assert(ctx->data < ctx->end); |
249 | 8.35k | i_assert(*ctx->data == '"'); |
250 | 8.35k | ctx->data++; |
251 | | |
252 | 87.1M | for (start = ctx->data; ctx->data < ctx->end; ctx->data++) { |
253 | 87.1M | switch (*ctx->data) { |
254 | 72.6M | case '\0': |
255 | 72.6M | if (ctx->nul_replacement_str != NULL) { |
256 | 72.6M | str_append_data(str, start, ctx->data - start); |
257 | 72.6M | str_append(str, ctx->nul_replacement_str); |
258 | 72.6M | start = ctx->data + 1; |
259 | 72.6M | } |
260 | 72.6M | break; |
261 | 5.55k | case '"': |
262 | 5.55k | str_append_data(str, start, ctx->data - start); |
263 | 5.55k | ctx->data++; |
264 | 5.55k | return rfc822_skip_lwsp(ctx); |
265 | 3.02k | case '\n': |
266 | | /* folding whitespace, remove the (CR)LF */ |
267 | 3.02k | len = ctx->data - start; |
268 | 3.02k | if (len > 0 && start[len-1] == '\r') |
269 | 711 | len--; |
270 | 3.02k | str_append_data(str, start, len); |
271 | 3.02k | start = ctx->data + 1; |
272 | 3.02k | break; |
273 | 6.21k | case '\\': |
274 | 6.21k | ctx->data++; |
275 | 6.21k | if (ctx->data >= ctx->end) |
276 | 865 | return -1; |
277 | | |
278 | 5.34k | if (*ctx->data == '\r' || *ctx->data == '\n' || |
279 | 4.82k | *ctx->data == '\0') { |
280 | | /* quoted-pair doesn't allow CR/LF/NUL. |
281 | | They are part of the obs-qp though, so don't |
282 | | return them as error. */ |
283 | 2.74k | ctx->data--; |
284 | 2.74k | break; |
285 | 2.74k | } |
286 | 2.60k | str_append_data(str, start, ctx->data - start - 1); |
287 | 2.60k | start = ctx->data; |
288 | 2.60k | break; |
289 | 87.1M | } |
290 | 87.1M | } |
291 | | |
292 | | /* missing '"' */ |
293 | 1.93k | return -1; |
294 | 8.35k | } |
295 | | |
296 | | static int |
297 | | rfc822_parse_atom_or_dot(struct rfc822_parser_context *ctx, string_t *str) |
298 | 0 | { |
299 | 0 | const unsigned char *start; |
300 | | |
301 | | /* |
302 | | atom = [CFWS] 1*atext [CFWS] |
303 | | atext = |
304 | | ; Any character except controls, SP, and specials. |
305 | | |
306 | | The difference between this function and rfc822_parse_dot_atom() |
307 | | is that this doesn't just silently skip over all the whitespace. |
308 | | */ |
309 | 0 | for (start = ctx->data; ctx->data < ctx->end; ctx->data++) { |
310 | 0 | if (IS_ATEXT(*ctx->data) || *ctx->data == '.') |
311 | 0 | continue; |
312 | | |
313 | 0 | str_append_data(str, start, ctx->data - start); |
314 | 0 | return rfc822_skip_lwsp(ctx); |
315 | 0 | } |
316 | | |
317 | 0 | str_append_data(str, start, ctx->data - start); |
318 | 0 | return 0; |
319 | 0 | } |
320 | | |
321 | | int rfc822_parse_phrase(struct rfc822_parser_context *ctx, string_t *str) |
322 | 0 | { |
323 | 0 | int ret; |
324 | | |
325 | | /* |
326 | | phrase = 1*word / obs-phrase |
327 | | word = atom / quoted-string |
328 | | obs-phrase = word *(word / "." / CFWS) |
329 | | */ |
330 | |
|
331 | 0 | if (ctx->data >= ctx->end) |
332 | 0 | return 0; |
333 | 0 | if (*ctx->data == '.') |
334 | 0 | return -1; |
335 | | |
336 | 0 | for (;;) { |
337 | 0 | if (*ctx->data == '"') |
338 | 0 | ret = rfc822_parse_quoted_string(ctx, str); |
339 | 0 | else |
340 | 0 | ret = rfc822_parse_atom_or_dot(ctx, str); |
341 | |
|
342 | 0 | if (ret <= 0) |
343 | 0 | return ret; |
344 | | |
345 | 0 | if (!IS_ATEXT(*ctx->data) && *ctx->data != '"' |
346 | 0 | && *ctx->data != '.') |
347 | 0 | break; |
348 | 0 | str_append_c(str, ' '); |
349 | 0 | } |
350 | 0 | return rfc822_skip_lwsp(ctx); |
351 | 0 | } |
352 | | |
353 | | static int |
354 | | rfc822_parse_domain_literal(struct rfc822_parser_context *ctx, string_t *str) |
355 | 0 | { |
356 | 0 | const unsigned char *start; |
357 | 0 | size_t len; |
358 | | |
359 | | /* |
360 | | domain-literal = [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS] |
361 | | dcontent = dtext / quoted-pair |
362 | | dtext = NO-WS-CTL / ; Non white space controls |
363 | | %d33-90 / ; The rest of the US-ASCII |
364 | | %d94-126 ; characters not including "[", |
365 | | ; "]", or "\" |
366 | | */ |
367 | 0 | i_assert(ctx->data < ctx->end); |
368 | 0 | i_assert(*ctx->data == '['); |
369 | | |
370 | 0 | for (start = ctx->data++; ctx->data < ctx->end; ctx->data++) { |
371 | 0 | switch (*ctx->data) { |
372 | 0 | case '\0': |
373 | 0 | if (ctx->nul_replacement_str != NULL) { |
374 | 0 | str_append_data(str, start, ctx->data - start); |
375 | 0 | str_append(str, ctx->nul_replacement_str); |
376 | 0 | start = ctx->data + 1; |
377 | 0 | } |
378 | 0 | break; |
379 | 0 | case '[': |
380 | | /* not allowed */ |
381 | 0 | return -1; |
382 | 0 | case ']': |
383 | 0 | str_append_data(str, start, ctx->data - start + 1); |
384 | 0 | ctx->data++; |
385 | 0 | return rfc822_skip_lwsp(ctx); |
386 | 0 | case '\n': |
387 | | /* folding whitespace, remove the (CR)LF */ |
388 | 0 | len = ctx->data - start; |
389 | 0 | if (len > 0 && start[len-1] == '\r') |
390 | 0 | len--; |
391 | 0 | str_append_data(str, start, len); |
392 | 0 | start = ctx->data + 1; |
393 | 0 | break; |
394 | 0 | case '\\': |
395 | | /* note: the '\' is preserved in the output */ |
396 | 0 | ctx->data++; |
397 | 0 | if (ctx->data >= ctx->end) |
398 | 0 | return -1; |
399 | | |
400 | 0 | if (*ctx->data == '\r' || *ctx->data == '\n' || |
401 | 0 | *ctx->data == '\0') { |
402 | | /* quoted-pair doesn't allow CR/LF/NUL. |
403 | | They are part of the obs-qp though, so don't |
404 | | return them as error. */ |
405 | 0 | str_append_data(str, start, ctx->data - start); |
406 | 0 | start = ctx->data; |
407 | 0 | ctx->data--; |
408 | 0 | break; |
409 | 0 | } |
410 | 0 | } |
411 | 0 | } |
412 | | |
413 | | /* missing ']' */ |
414 | 0 | return -1; |
415 | 0 | } |
416 | | |
417 | | int rfc822_parse_domain(struct rfc822_parser_context *ctx, string_t *str) |
418 | 0 | { |
419 | | /* |
420 | | domain = dot-atom / domain-literal / obs-domain |
421 | | domain-literal = [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS] |
422 | | obs-domain = atom *("." atom) |
423 | | */ |
424 | 0 | i_assert(ctx->data < ctx->end); |
425 | 0 | i_assert(*ctx->data == '@'); |
426 | 0 | ctx->data++; |
427 | |
|
428 | 0 | if (rfc822_skip_lwsp(ctx) <= 0) |
429 | 0 | return -1; |
430 | | |
431 | 0 | if (*ctx->data == '[') |
432 | 0 | return rfc822_parse_domain_literal(ctx, str); |
433 | 0 | else { |
434 | | #ifdef EXPERIMENTAL_MAIL_UTF8 |
435 | | size_t start_pos = str_len(str); |
436 | | #endif |
437 | 0 | int ret = rfc822_parse_dot_atom(ctx, str); |
438 | | #ifdef EXPERIMENTAL_MAIL_UTF8 |
439 | | if (ret < 0) |
440 | | return -1; |
441 | | |
442 | | const char *domain = str_c(str) + start_pos, *domain_unicode; |
443 | | const char *error; |
444 | | |
445 | | if (idna_process_domain_name(domain, 0, &domain_unicode, NULL, |
446 | | &error) == 0) { |
447 | | str_truncate(str, start_pos); |
448 | | str_append(str, domain_unicode); |
449 | | } |
450 | | #endif |
451 | 0 | return ret; |
452 | 0 | } |
453 | 0 | } |
454 | | |
455 | | int rfc822_parse_content_type(struct rfc822_parser_context *ctx, string_t *str) |
456 | 48.9k | { |
457 | 48.9k | size_t str_pos_0 = str->used; |
458 | 48.9k | if (rfc822_skip_lwsp(ctx) <= 0) |
459 | 3.03k | return -1; |
460 | | |
461 | | /* get main type, require at least one byte */ |
462 | 45.9k | if (rfc822_parse_mime_token(ctx, str) <= 0 || |
463 | 43.7k | str->used == str_pos_0) |
464 | 2.65k | return -1; |
465 | | |
466 | | /* skip over "/" */ |
467 | 43.2k | if (*ctx->data != '/') { |
468 | 2.09k | str_truncate(str, str_pos_0); |
469 | 2.09k | return -1; |
470 | 2.09k | } |
471 | 41.1k | ctx->data++; |
472 | 41.1k | if (rfc822_skip_lwsp(ctx) <= 0) { |
473 | 4.81k | str_truncate(str, str_pos_0); |
474 | 4.81k | return -1; |
475 | 4.81k | } |
476 | 36.3k | str_append_c(str, '/'); |
477 | | |
478 | 36.3k | size_t str_pos = str->used; |
479 | | /* get subtype, require at least one byte, |
480 | | and check the next separator to avoid accepting |
481 | | invalid values. */ |
482 | 36.3k | int ret; |
483 | 36.3k | if ((ret = rfc822_parse_mime_token(ctx, str)) < 0 || |
484 | 35.8k | str->used == str_pos || |
485 | 35.2k | (ctx->data != ctx->end && *ctx->data != ';')) { |
486 | 1.65k | str_truncate(str, str_pos_0); |
487 | 1.65k | return -1; |
488 | 1.65k | } |
489 | 34.6k | return ret; |
490 | 36.3k | } |
491 | | |
492 | | int rfc822_parse_content_param(struct rfc822_parser_context *ctx, |
493 | | const char **key_r, string_t *value) |
494 | 514k | { |
495 | 514k | string_t *key; |
496 | 514k | int ret; |
497 | | |
498 | | /* .. := *(";" parameter) |
499 | | parameter := attribute "=" value |
500 | | attribute := token |
501 | | value := token / quoted-string |
502 | | */ |
503 | 514k | *key_r = NULL; |
504 | 514k | str_truncate(value, 0); |
505 | | |
506 | 514k | if (ctx->data >= ctx->end) |
507 | 22.7k | return 0; |
508 | 491k | if (*ctx->data != ';') |
509 | 283k | return -1; |
510 | 207k | ctx->data++; |
511 | | |
512 | 207k | if (rfc822_skip_lwsp(ctx) <= 0) |
513 | 3.19k | return -1; |
514 | | |
515 | 204k | key = t_str_new(64); |
516 | 204k | if (rfc822_parse_mime_token(ctx, key) <= 0) |
517 | 1.83k | return -1; |
518 | | |
519 | 202k | if (*ctx->data != '=') |
520 | 18.4k | return -1; |
521 | 184k | ctx->data++; |
522 | | |
523 | 184k | if ((ret = rfc822_skip_lwsp(ctx)) <= 0) { |
524 | | /* broken / no value */ |
525 | 169k | } else if (*ctx->data == '"') { |
526 | 8.35k | ret = rfc822_parse_quoted_string(ctx, value); |
527 | 161k | } else if (ctx->data < ctx->end && *ctx->data == '=') { |
528 | | /* workaround for broken input: |
529 | | name==?utf-8?b?...?= */ |
530 | 23.7k | while (ctx->data < ctx->end && *ctx->data != ';' && |
531 | 21.0k | *ctx->data != ' ' && *ctx->data != '\t' && |
532 | 20.3k | *ctx->data != '\r' && *ctx->data != '\n') { |
533 | 20.1k | str_append_c(value, *ctx->data); |
534 | 20.1k | ctx->data++; |
535 | 20.1k | } |
536 | 157k | } else { |
537 | 157k | ret = rfc822_parse_mime_token(ctx, value); |
538 | 157k | } |
539 | | |
540 | 184k | *key_r = str_c(key); |
541 | 184k | return ret < 0 ? -1 : 1; |
542 | 202k | } |