File Coverage

dovecot-parser.c
Criterion Covered Total %
statement 438 549 79.7
branch 246 370 66.4
condition n/a
subroutine n/a
pod n/a
total 684 919 74.4


line stmt bran cond sub pod time code
1             /*
2             * Copyright (c) 2002-2017 Dovecot authors
3             * Copyright (c) 2015-2017 Pali
4             *
5             * Permission is hereby granted, free of charge, to any person obtaining a
6             * copy of this software and associated documentation files (the "Software"),
7             * to deal in the Software without restriction, including without limitation
8             * the rights to use, copy, modify, merge, publish, distribute, sublicense,
9             * and/or sell copies of the Software, and to permit persons to whom the
10             * Software is furnished to do so, subject to the following conditions:
11             *
12             * The above copyright notice and this permission notice shall be included in
13             * all copies or substantial portions of the Software.
14             *
15             * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16             * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17             * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18             * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19             * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20             * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21             * DEALINGS IN THE SOFTWARE.
22             */
23              
24             #include
25             #include
26             #include
27             #include
28             #include
29              
30             #include "dovecot-parser.h"
31              
32             #ifndef SIZE_MAX
33             #define SIZE_MAX ((size_t)-1)
34             #endif
35              
36             void i_panic(const char *format, ...);
37              
38             #ifdef DEBUG
39             #define i_assert(expr) \
40             do { if (!(expr)) \
41             i_panic("file %s: line %d (%s): assertion failed: (%s)", \
42             __FILE__, \
43             __LINE__, \
44             __FUNCTION__, \
45             #expr); \
46             } while ( 0 )
47             #else
48             #define i_assert(expr)
49             #endif
50              
51             typedef struct {
52             char *buf;
53             size_t len;
54             size_t size;
55             } string_t;
56              
57             struct rfc822_parser_context {
58             const unsigned char *data, *end;
59             string_t *last_comment;
60             };
61              
62             struct message_address_parser_context {
63             struct rfc822_parser_context parser;
64              
65             struct message_address *first_addr, *last_addr, addr;
66             string_t *str;
67              
68             bool fill_missing;
69             };
70              
71 321           static string_t *str_new(size_t initial_size)
72             {
73             char *buf;
74             string_t *str;
75              
76 321 50         if (!initial_size)
77 0           initial_size = 1;
78              
79 321 50         if (initial_size >= SIZE_MAX / 2)
80 0           i_panic("str_new() failed: %s", "initial_size is too big");
81              
82 321           buf = malloc(initial_size);
83 321 50         if (!buf)
84 0           i_panic("malloc() failed: %s", strerror(errno));
85              
86 321           str = malloc(sizeof(string_t));
87 321 50         if (!str)
88 0           i_panic("malloc() failed: %s", strerror(errno));
89              
90 321           buf[0] = 0;
91              
92 321           str->buf = buf;
93 321           str->len = 0;
94 321           str->size = initial_size;
95              
96 321           return str;
97             }
98              
99 321           static void str_free(string_t **str)
100             {
101 321           free((*str)->buf);
102 321           free(*str);
103 321           *str = NULL;
104 321           }
105              
106 571           static const char *str_c(string_t *str)
107             {
108 571           return str->buf;
109             }
110              
111 100           static size_t str_len(const string_t *str)
112             {
113 100           return str->len;
114             }
115              
116 1871           static void str_append_data(string_t *str, const void *data, size_t len)
117             {
118             char *new_buf;
119             size_t need_size;
120              
121 1871           need_size = str->len + len + 1;
122              
123 1871 50         if (len >= SIZE_MAX / 2 || need_size >= SIZE_MAX / 2)
    50          
124 0           i_panic("%s() failed: %s", __FUNCTION__, "len is too big");
125              
126 1871 100         if (need_size > str->size) {
127 6           str->size = 1;
128 56 100         while (str->size < need_size)
129 50           str->size <<= 1;
130              
131 6           new_buf = realloc(str->buf, str->size);
132 6 50         if (!new_buf)
133 0           i_panic("realloc() failed: %s", strerror(errno));
134              
135 6           str->buf = new_buf;
136             }
137              
138 1871           memcpy(str->buf + str->len, data, len);
139 1871           str->len += len;
140 1871           str->buf[str->len] = 0;
141 1871           }
142              
143 260           static void str_append(string_t *str, const char *cstr)
144             {
145 260           str_append_data(str, cstr, strlen(cstr));
146 260           }
147              
148 727           static void str_append_c(string_t *str, unsigned char chr)
149             {
150 727           str_append_data(str, &chr, 1);
151 727           }
152              
153 660           static void str_append_n(string_t *str, const void *cstr, size_t max_len)
154             {
155             size_t len;
156              
157 660           len = 0;
158 6118 100         while (len < max_len && ((const char *)cstr)[len] != '\0')
    50          
159 5458           len++;
160              
161 660           str_append_data(str, cstr, len);
162 660           }
163              
164 516           static void str_truncate(string_t *str, size_t len)
165             {
166 516 50         if (str->size - 1 <= len || str->len <= len)
    100          
167 192           return;
168              
169 324           str->len = len;
170 324           str->buf[len] = 0;
171             }
172              
173             /*
174             atext = ALPHA / DIGIT / ; Any character except controls,
175             "!" / "#" / ; SP, and specials.
176             "$" / "%" / ; Used for atoms
177             "&" / "'" /
178             "*" / "+" /
179             "-" / "/" /
180             "=" / "?" /
181             "^" / "_" /
182             "`" / "{" /
183             "|" / "}" /
184             "~"
185              
186             MIME:
187              
188             token := 1*
189             or tspecials>
190             tspecials := "(" / ")" / "<" / ">" / "@" /
191             "," / ";" / ":" / "\" / <">
192             "/" / "[" / "]" / "?" / "="
193              
194             So token is same as dot-atom, except stops also at '/', '?' and '='.
195             */
196              
197             /* atext chars are marked with 1, alpha and digits with 2,
198             atext-but-mime-tspecials with 4 */
199             unsigned char rfc822_atext_chars[256] = {
200             0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0-15 */
201             0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16-31 */
202             0, 1, 0, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 1, 0, 4, /* 32-47 */
203             2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 4, 0, 4, /* 48-63 */
204             0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 64-79 */
205             2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 0, 0, 0, 1, 1, /* 80-95 */
206             1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 96-111 */
207             2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 0, /* 112-127 */
208              
209             2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
210             2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
211             2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
212             2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
213             2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
214             2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
215             2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
216             2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2
217             };
218              
219             #define IS_ATEXT(c) \
220             (rfc822_atext_chars[(int)(unsigned char)(c)] != 0)
221             #define IS_ATEXT_NON_TSPECIAL(c) \
222             ((rfc822_atext_chars[(int)(unsigned char)(c)] & 3) != 0)
223              
224             #define IS_ESCAPED_CHAR(c) ((c) == '"' || (c) == '\\' || (c) == '\'')
225              
226             /* quote with "" and escape all '\', '"' and "'" characters if need */
227 224           static void str_append_maybe_escape(string_t *str, const char *cstr, bool escape_dot)
228             {
229             const char *p;
230              
231             /* see if we need to quote it */
232 2167 100         for (p = cstr; *p != '\0'; p++) {
233 1975 100         if (!IS_ATEXT(*p) && (escape_dot || *p != '.'))
    100          
    100          
234             break;
235             }
236              
237 224 100         if (*p == '\0') {
238 192           str_append_data(str, cstr, (size_t) (p - cstr));
239 192           return;
240             }
241              
242             /* see if we need to escape it */
243 426 100         for (p = cstr; *p != '\0'; p++) {
244 402 100         if (IS_ESCAPED_CHAR(*p))
    50          
    50          
245             break;
246             }
247              
248 32 100         if (*p == '\0') {
249             /* only quote */
250 24           str_append_c(str, '"');
251 24           str_append_data(str, cstr, (size_t) (p - cstr));
252 24           str_append_c(str, '"');
253 24           return;
254             }
255              
256             /* quote and escape */
257 8           str_append_c(str, '"');
258 8           str_append_data(str, cstr, (size_t) (p - cstr));
259              
260 117 100         for (; *p != '\0'; p++) {
261 109 100         if (IS_ESCAPED_CHAR(*p))
    50          
    50          
262 13           str_append_c(str, '\\');
263 109           str_append_c(str, *p);
264             }
265              
266 8           str_append_c(str, '"');
267             }
268              
269             /* Parse given data using RFC 822 token parser. */
270 132           static void rfc822_parser_init(struct rfc822_parser_context *ctx,
271             const unsigned char *data, size_t size,
272             string_t *last_comment)
273             {
274 132           memset(ctx, 0, sizeof(*ctx));
275 132           ctx->data = data;
276 132           ctx->end = data + size;
277 132           ctx->last_comment = last_comment;
278 132           }
279              
280             /* The functions below return 1 = more data available, 0 = no more data
281             available (but a value might have been returned now), -1 = invalid input.
282              
283             LWSP is automatically skipped after value, but not before it. So typically
284             you begin with skipping LWSP and then start using the parse functions. */
285              
286             /* Parse comment. Assumes parser's data points to '(' */
287 38           static int rfc822_skip_comment(struct rfc822_parser_context *ctx)
288             {
289             const unsigned char *start;
290 38           int level = 1;
291              
292             i_assert(*ctx->data == '(');
293              
294 38 100         if (ctx->last_comment != NULL)
295 25           str_truncate(ctx->last_comment, 0);
296              
297 38           start = ++ctx->data;
298 623 50         for (; ctx->data != ctx->end; ctx->data++) {
299 623           switch (*ctx->data) {
300             case '(':
301 14           level++;
302 14           break;
303             case ')':
304 52 100         if (--level == 0) {
305 38 100         if (ctx->last_comment != NULL) {
306 25           str_append_n(ctx->last_comment, start,
307 25           ctx->data - start);
308             }
309 38           ctx->data++;
310 38           return ctx->data != ctx->end ? 1 : 0;
311             }
312 14           break;
313             case '\\':
314 0 0         if (ctx->last_comment != NULL) {
315 0           str_append_n(ctx->last_comment, start,
316 0           ctx->data - start);
317             }
318 0           start = ctx->data + 1;
319              
320 0           ctx->data++;
321 0 0         if (ctx->data == ctx->end)
322 0           return -1;
323 0           break;
324             }
325             }
326              
327             /* missing ')' */
328 0           return -1;
329             }
330              
331             /* Skip LWSP if there is any */
332 1213           static int rfc822_skip_lwsp(struct rfc822_parser_context *ctx)
333             {
334 1419 100         for (; ctx->data != ctx->end;) {
335 1386 100         if (*ctx->data == ' ' || *ctx->data == '\t' ||
    50          
    50          
336 1219 100         *ctx->data == '\r' || *ctx->data == '\n') {
337 168           ctx->data++;
338 168           continue;
339             }
340              
341 1218 100         if (*ctx->data != '(')
342 1180           break;
343              
344 38 50         if (rfc822_skip_comment(ctx) < 0)
345 0           return -1;
346             }
347 1213           return ctx->data != ctx->end ? 1 : 0;
348             }
349              
350             /* Like parse_atom() but don't stop at '.' */
351 288           static int rfc822_parse_dot_atom(struct rfc822_parser_context *ctx, string_t *str)
352             {
353             const unsigned char *start;
354             int ret;
355              
356             /*
357             dot-atom = [CFWS] dot-atom-text [CFWS]
358             dot-atom-text = 1*atext *("." 1*atext)
359              
360             atext =
361             ; Any character except controls, SP, and specials.
362              
363             For RFC-822 compatibility allow LWSP around '.'
364             */
365 288 50         if (ctx->data == ctx->end || !IS_ATEXT(*ctx->data))
    100          
366 1           return -1;
367              
368 3285 100         for (start = ctx->data++; ctx->data != ctx->end; ) {
369 3191 100         if (IS_ATEXT(*ctx->data)) {
370 2811           ctx->data++;
371 2811           continue;
372             }
373              
374 380           str_append_n(str, start, ctx->data - start);
375              
376 380 100         if ((ret = rfc822_skip_lwsp(ctx)) <= 0)
377 6           return ret;
378              
379 374 100         if (*ctx->data != '.')
380 187           return 1;
381              
382 187           ctx->data++;
383 187           str_append_c(str, '.');
384              
385 187 50         if ((ret = rfc822_skip_lwsp(ctx)) <= 0)
386 0           return ret;
387 187           start = ctx->data;
388             }
389              
390 94           str_append_n(str, start, ctx->data - start);
391 94           return 0;
392             }
393              
394             /* "quoted string" */
395 62           static int rfc822_parse_quoted_string(struct rfc822_parser_context *ctx, string_t *str)
396             {
397             const unsigned char *start;
398             size_t len;
399              
400             i_assert(*ctx->data == '"');
401 62           ctx->data++;
402              
403 1263 50         for (start = ctx->data; ctx->data != ctx->end; ctx->data++) {
404 1263           switch (*ctx->data) {
405             case '"':
406 62           str_append_n(str, start, ctx->data - start);
407 62           ctx->data++;
408 62           return rfc822_skip_lwsp(ctx);
409             case '\n':
410             /* folding whitespace, remove the (CR)LF */
411 0           len = ctx->data - start;
412 0 0         if (len > 0 && start[len-1] == '\r')
    0          
413 0           len--;
414 0           str_append_n(str, start, len);
415 0           start = ctx->data + 1;
416 0           break;
417             case '\\':
418 27           ctx->data++;
419 27 50         if (ctx->data == ctx->end)
420 0           return -1;
421              
422 27           str_append_n(str, start, ctx->data - start - 1);
423 27           start = ctx->data;
424 27           break;
425             }
426             }
427              
428             /* missing '"' */
429 0           return -1;
430             }
431              
432             static int
433 72           rfc822_parse_atom_or_dot(struct rfc822_parser_context *ctx, string_t *str)
434             {
435             const unsigned char *start;
436              
437             /*
438             atom = [CFWS] 1*atext [CFWS]
439             atext =
440             ; Any character except controls, SP, and specials.
441              
442             The difference between this function and rfc822_parse_dot_atom()
443             is that this doesn't just silently skip over all the whitespace.
444             */
445 743 100         for (start = ctx->data; ctx->data != ctx->end; ctx->data++) {
446 737 100         if (IS_ATEXT(*ctx->data) || *ctx->data == '.')
    100          
447 671           continue;
448              
449 66           str_append_n(str, start, ctx->data - start);
450 66           return rfc822_skip_lwsp(ctx);
451             }
452              
453 6           str_append_n(str, start, ctx->data - start);
454 6           return 0;
455             }
456              
457             /* atom or quoted-string */
458 119           static int rfc822_parse_phrase(struct rfc822_parser_context *ctx, string_t *str)
459             {
460             int ret;
461              
462             /*
463             phrase = 1*word / obs-phrase
464             word = atom / quoted-string
465             obs-phrase = word *(word / "." / CFWS)
466             */
467              
468 119 50         if (ctx->data == ctx->end)
469 0           return 0;
470 119 50         if (*ctx->data == '.')
471 0           return -1;
472              
473             for (;;) {
474 130 100         if (*ctx->data == '"')
475 58           ret = rfc822_parse_quoted_string(ctx, str);
476             else
477 72           ret = rfc822_parse_atom_or_dot(ctx, str);
478              
479 130 100         if (ret <= 0)
480 6           return ret;
481              
482 124 100         if (!IS_ATEXT(*ctx->data) && *ctx->data != '"'
    100          
483 113 50         && *ctx->data != '.')
484 113           break;
485 11           str_append_c(str, ' ');
486 11           }
487 113           return rfc822_skip_lwsp(ctx);
488             }
489              
490             static int
491 0           rfc822_parse_domain_literal(struct rfc822_parser_context *ctx, string_t *str)
492             {
493             const unsigned char *start;
494              
495             /*
496             domain-literal = [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS]
497             dcontent = dtext / quoted-pair
498             dtext = NO-WS-CTL / ; Non white space controls
499             %d33-90 / ; The rest of the US-ASCII
500             %d94-126 ; characters not including "[",
501             ; "]", or "\"
502             */
503             i_assert(*ctx->data == '[');
504              
505 0 0         for (start = ctx->data; ctx->data != ctx->end; ctx->data++) {
506 0 0         if (*ctx->data == '\\') {
507 0           ctx->data++;
508 0 0         if (ctx->data == ctx->end)
509 0           break;
510 0 0         } else if (*ctx->data == ']') {
511 0           ctx->data++;
512 0           str_append_n(str, start, ctx->data - start);
513 0           return rfc822_skip_lwsp(ctx);
514             }
515             }
516              
517             /* missing ']' */
518 0           return -1;
519             }
520              
521             /* dot-atom / domain-literal */
522 141           static int rfc822_parse_domain(struct rfc822_parser_context *ctx, string_t *str)
523             {
524             /*
525             domain = dot-atom / domain-literal / obs-domain
526             domain-literal = [CFWS] "[" *([FWS] dcontent) [FWS] "]" [CFWS]
527             obs-domain = atom *("." atom)
528             */
529             i_assert(*ctx->data == '@');
530 141           ctx->data++;
531              
532 141 50         if (rfc822_skip_lwsp(ctx) <= 0)
533 0           return -1;
534              
535 141 50         if (*ctx->data == '[')
536 0           return rfc822_parse_domain_literal(ctx, str);
537             else
538 141           return rfc822_parse_dot_atom(ctx, str);
539             }
540              
541 78           static void add_address(struct message_address_parser_context *ctx)
542             {
543             struct message_address *addr;
544              
545 78           addr = malloc(sizeof(struct message_address));
546 78 50         if (!addr)
547 0           i_panic("malloc() failed: %s", strerror(errno));
548              
549 78           memcpy(addr, &ctx->addr, sizeof(ctx->addr));
550 78           memset(&ctx->addr, 0, sizeof(ctx->addr));
551              
552 78 100         if (ctx->first_addr == NULL)
553 33           ctx->first_addr = addr;
554             else
555 45           ctx->last_addr->next = addr;
556 78           ctx->last_addr = addr;
557 78           }
558              
559 151           static int parse_local_part(struct message_address_parser_context *ctx)
560             {
561             int ret;
562              
563             /*
564             local-part = dot-atom / quoted-string / obs-local-part
565             obs-local-part = word *("." word)
566             */
567             i_assert(ctx->parser.data != ctx->parser.end);
568              
569 151           str_truncate(ctx->str, 0);
570 151 100         if (*ctx->parser.data == '"')
571 4           ret = rfc822_parse_quoted_string(&ctx->parser, ctx->str);
572             else
573 147           ret = rfc822_parse_dot_atom(&ctx->parser, ctx->str);
574 151 100         if (ret < 0)
575 1           return -1;
576              
577 150           ctx->addr.mailbox = strdup(str_c(ctx->str));
578 150           return ret;
579             }
580              
581 141           static int parse_domain(struct message_address_parser_context *ctx)
582             {
583             int ret;
584              
585 141           str_truncate(ctx->str, 0);
586 141 50         if ((ret = rfc822_parse_domain(&ctx->parser, ctx->str)) < 0)
587 0           return -1;
588              
589 141           ctx->addr.domain = strdup(str_c(ctx->str));
590 141           return ret;
591             }
592              
593 0           static int parse_domain_list(struct message_address_parser_context *ctx)
594             {
595             int ret;
596              
597             /* obs-domain-list = "@" domain *(*(CFWS / "," ) [CFWS] "@" domain) */
598 0           str_truncate(ctx->str, 0);
599             for (;;) {
600 0 0         if (ctx->parser.data == ctx->parser.end)
601 0           return 0;
602              
603 0 0         if (*ctx->parser.data != '@')
604 0           break;
605              
606 0 0         if (str_len(ctx->str) > 0)
607 0           str_append_c(ctx->str, ',');
608              
609 0           str_append_c(ctx->str, '@');
610 0 0         if ((ret = rfc822_parse_domain(&ctx->parser, ctx->str)) <= 0)
611 0           return ret;
612              
613 0 0         while (rfc822_skip_lwsp(&ctx->parser) > 0 &&
    0          
614 0           *ctx->parser.data == ',')
615 0           ctx->parser.data++;
616 0           }
617 0           ctx->addr.route = strdup(str_c(ctx->str));
618 0           return 1;
619             }
620              
621 43           static int parse_angle_addr(struct message_address_parser_context *ctx)
622             {
623             int ret;
624              
625             /* "<" [ "@" route ":" ] local-part "@" domain ">" */
626             i_assert(*ctx->parser.data == '<');
627 43           ctx->parser.data++;
628              
629 43 50         if ((ret = rfc822_skip_lwsp(&ctx->parser)) <= 0)
630 0           return ret;
631              
632 43 50         if (*ctx->parser.data == '@') {
633 0 0         if (parse_domain_list(ctx) <= 0 || *ctx->parser.data != ':') {
    0          
634 0 0         if (ctx->fill_missing)
635 0           ctx->addr.route = strdup("INVALID_ROUTE");
636 0           ctx->addr.invalid_syntax = true;
637 0 0         if (ctx->parser.data == ctx->parser.end)
638 0           return -1;
639             /* try to continue anyway */
640             } else {
641 0           ctx->parser.data++;
642             }
643 0           ctx->parser.data++;
644 0 0         if ((ret = rfc822_skip_lwsp(&ctx->parser)) <= 0)
645 0           return ret;
646             }
647              
648 43 50         if (*ctx->parser.data == '>') {
649             /* <> address isn't valid */
650             } else {
651 43 50         if ((ret = parse_local_part(ctx)) <= 0)
652 0           return ret;
653 43 50         if (*ctx->parser.data == '@') {
654 43 50         if ((ret = parse_domain(ctx)) <= 0)
655 0           return ret;
656             }
657             }
658              
659 43 50         if (*ctx->parser.data != '>')
660 0           return -1;
661 43           ctx->parser.data++;
662              
663 43           return rfc822_skip_lwsp(&ctx->parser);
664             }
665              
666 60           static int parse_name_addr(struct message_address_parser_context *ctx)
667             {
668             /*
669             name-addr = [display-name] angle-addr
670             display-name = phrase
671             */
672 60           str_truncate(ctx->str, 0);
673 60 100         if (rfc822_parse_phrase(&ctx->parser, ctx->str) <= 0 ||
    100          
674 57           *ctx->parser.data != '<')
675 17           return -1;
676              
677 43 100         if (*str_c(ctx->str) == '\0') {
678             /* Cope with "
" without display name */
679 1           ctx->addr.name = NULL;
680             } else {
681 42           ctx->addr.name = strdup(str_c(ctx->str));
682             }
683              
684 43 50         if (ctx->parser.last_comment != NULL)
685 43           str_truncate(ctx->parser.last_comment, 0);
686              
687 43 50         if (parse_angle_addr(ctx) < 0) {
688             /* broken */
689 0 0         if (ctx->fill_missing)
690 0           ctx->addr.domain = strdup("SYNTAX_ERROR");
691 0           ctx->addr.invalid_syntax = true;
692             }
693              
694 43 50         if (ctx->parser.last_comment != NULL) {
695 43 100         if (str_len(ctx->parser.last_comment) > 0) {
696 7           ctx->addr.comment =
697 7           strdup(str_c(ctx->parser.last_comment));
698             }
699             }
700              
701 43           return ctx->parser.data != ctx->parser.end ? 1 : 0;
702             }
703              
704 108           static int parse_addr_spec(struct message_address_parser_context *ctx)
705             {
706             /* addr-spec = local-part "@" domain */
707 108           int ret, ret2 = -2;
708              
709             i_assert(ctx->parser.data != ctx->parser.end);
710              
711 108 100         if (ctx->parser.last_comment != NULL)
712 17           str_truncate(ctx->parser.last_comment, 0);
713              
714             #if 0
715             bool quoted_string = *ctx->parser.data == '"';
716             #endif
717 108           ret = parse_local_part(ctx);
718 108 100         if (ret <= 0) {
719             /* end of input or parsing local-part failed */
720 8           ctx->addr.invalid_syntax = true;
721             }
722 108 100         if (ret != 0 && *ctx->parser.data == '@') {
    100          
723 98           ret2 = parse_domain(ctx);
724 98 100         if (ret2 <= 0)
725 93           ret = ret2;
726             }
727              
728 108 100         if (ctx->parser.last_comment != NULL && str_len(ctx->parser.last_comment) > 0)
    100          
729 3           ctx->addr.comment = strdup(str_c(ctx->parser.last_comment));
730             else if (ret2 == -2) {
731             #if 0
732             /* So far we've read user without @domain and without
733             (Display Name). We'll assume that a single "user" (already
734             read into addr.mailbox) is a mailbox, but if it's followed
735             by anything else it's a display-name. */
736             str_append_c(ctx->str, ' ');
737             size_t orig_str_len = str_len(ctx->str);
738             (void)rfc822_parse_phrase(&ctx->parser, ctx->str);
739             if (str_len(ctx->str) != orig_str_len) {
740             ctx->addr.mailbox = NULL;
741             ctx->addr.name = strdup(str_c(ctx->str));
742             } else {
743             if (!quoted_string)
744             ctx->addr.domain = strdup("");
745             }
746             ctx->addr.invalid_syntax = true;
747             ret = -1;
748             #endif
749             }
750 108           return ret;
751             }
752              
753 60           static void add_fixed_address(struct message_address_parser_context *ctx)
754             {
755 60 100         if (ctx->addr.mailbox == NULL) {
756 3 50         ctx->addr.mailbox = strdup(!ctx->fill_missing ? "" : "MISSING_MAILBOX");
757 3           ctx->addr.invalid_syntax = true;
758             }
759 60 100         if (ctx->addr.domain == NULL || ctx->addr.domain[0] == '\0') {
    50          
760 4 50         ctx->addr.domain = strdup(!ctx->fill_missing ? "" : "MISSING_DOMAIN");
761 4           ctx->addr.invalid_syntax = true;
762             }
763 60           add_address(ctx);
764 60           }
765              
766 60           static int parse_mailbox(struct message_address_parser_context *ctx)
767             {
768             const unsigned char *start;
769             size_t len;
770             int ret;
771              
772             /* mailbox = name-addr / addr-spec */
773 60           start = ctx->parser.data;
774 60 100         if ((ret = parse_name_addr(ctx)) < 0) {
775             /* nope, should be addr-spec */
776 17 50         if (ctx->addr.name != NULL) {
777 0           free(ctx->addr.name);
778 0           ctx->addr.name = NULL;
779             }
780 17 50         if (ctx->addr.route != NULL) {
781 0           free(ctx->addr.route);
782 0           ctx->addr.route = NULL;
783             }
784 17 50         if (ctx->addr.mailbox != NULL) {
785 0           free(ctx->addr.mailbox);
786 0           ctx->addr.mailbox = NULL;
787             }
788 17 50         if (ctx->addr.domain != NULL) {
789 0           free(ctx->addr.domain);
790 0           ctx->addr.domain = NULL;
791             }
792 17 50         if (ctx->addr.comment != NULL) {
793 0           free(ctx->addr.comment);
794 0           ctx->addr.comment = NULL;
795             }
796 17 50         if (ctx->addr.original != NULL) {
797 0           free(ctx->addr.original);
798 0           ctx->addr.original = NULL;
799             }
800 17           ctx->parser.data = start;
801 17           ret = parse_addr_spec(ctx);
802 17 100         if (ctx->addr.invalid_syntax && ctx->addr.name == NULL &&
    50          
    50          
803 3 50         ctx->addr.mailbox != NULL && ctx->addr.domain == NULL) {
804 3           ctx->addr.name = ctx->addr.mailbox;
805 3           ctx->addr.mailbox = NULL;
806             }
807             }
808              
809 60 50         if (ret < 0)
810 0           ctx->addr.invalid_syntax = true;
811              
812 60           len = ctx->parser.data - start;
813 60           ctx->addr.original = malloc(len + 1);
814 60 50         if (!ctx->addr.original)
815 0           i_panic("malloc() failed: %s", strerror(errno));
816              
817 60           memcpy(ctx->addr.original, start, len);
818 60           ctx->addr.original[len] = 0;
819              
820 60           add_fixed_address(ctx);
821              
822 60           free(ctx->addr.original);
823 60           ctx->addr.original = NULL;
824 60           return ret;
825             }
826              
827 59           static int parse_group(struct message_address_parser_context *ctx)
828             {
829             int ret;
830              
831             /*
832             group = display-name ":" [mailbox-list / CFWS] ";" [CFWS]
833             display-name = phrase
834             */
835 59           str_truncate(ctx->str, 0);
836 59 100         if (rfc822_parse_phrase(&ctx->parser, ctx->str) <= 0 ||
    100          
837 56           *ctx->parser.data != ':')
838 50           return -1;
839              
840             /* from now on don't return -1 even if there are problems, so that
841             the caller knows this is a group */
842 9           ctx->parser.data++;
843 9 50         if ((ret = rfc822_skip_lwsp(&ctx->parser)) <= 0)
844 0           ctx->addr.invalid_syntax = true;
845              
846 9           ctx->addr.mailbox = strdup(str_c(ctx->str));
847 9           add_address(ctx);
848              
849 9 50         if (ret > 0 && *ctx->parser.data != ';') {
    100          
850             for (;;) {
851             /* mailbox-list =
852             (mailbox *("," mailbox)) / obs-mbox-list */
853 10           if (parse_mailbox(ctx) <= 0) {
854             /* broken mailbox - try to continue anyway. */
855             }
856 10 100         if (ctx->parser.data == ctx->parser.end ||
    100          
857 9           *ctx->parser.data != ',')
858             break;
859 3           ctx->parser.data++;
860 3 50         if (rfc822_skip_lwsp(&ctx->parser) <= 0) {
861 0           ret = -1;
862 0           break;
863             }
864 3           }
865             }
866 9 50         if (ret >= 0) {
867 9 100         if (ctx->parser.data == ctx->parser.end ||
    50          
868 8           *ctx->parser.data != ';')
869 1           ret = -1;
870             else {
871 8           ctx->parser.data++;
872 8           ret = rfc822_skip_lwsp(&ctx->parser);
873             }
874             }
875 9 100         if (ret < 0)
876 1           ctx->addr.invalid_syntax = true;
877              
878 9           add_address(ctx);
879 9           return ret == 0 ? 0 : 1;
880             }
881              
882 59           static int parse_address(struct message_address_parser_context *ctx)
883             {
884             const unsigned char *start;
885             int ret;
886              
887             /* address = mailbox / group */
888 59           start = ctx->parser.data;
889 59 100         if ((ret = parse_group(ctx)) < 0) {
890             /* not a group, try mailbox */
891 50           ctx->parser.data = start;
892 50           ret = parse_mailbox(ctx);
893             }
894 59           return ret;
895             }
896              
897 33           static int parse_address_list(struct message_address_parser_context *ctx,
898             unsigned int max_addresses)
899             {
900             const unsigned char *start;
901             size_t len;
902 33           int ret = 0;
903              
904             /* address-list = (address *("," address)) / obs-addr-list */
905 59 50         while (max_addresses > 0) {
906 59           max_addresses--;
907 59 100         if ((ret = parse_address(ctx)) == 0)
908 31           break;
909 28 100         if (ctx->parser.data == ctx->parser.end ||
    100          
910 27           *ctx->parser.data != ',') {
911 2           ret = -1;
912 2           break;
913             }
914 26           ctx->parser.data++;
915 26           start = ctx->parser.data;
916 26 50         if ((ret = rfc822_skip_lwsp(&ctx->parser)) <= 0) {
917 0 0         if (ret < 0) {
918             /* ends with some garbage */
919 0           len = ctx->parser.data - start;
920 0           ctx->addr.original = malloc(len + 1);
921 0 0         if (!ctx->addr.original)
922 0           i_panic("malloc() failed: %s", strerror(errno));
923              
924 0           memcpy(ctx->addr.original, start, len);
925 0           ctx->addr.original[len] = 0;
926              
927 0           add_fixed_address(ctx);
928              
929 0           free(ctx->addr.original);
930 0           ctx->addr.original = NULL;
931             }
932 0           break;
933             }
934             }
935 33           return ret;
936             }
937              
938 107           void message_address_add(struct message_address **first, struct message_address **last,
939             const char *name, const char *route, const char *mailbox,
940             const char *domain, const char * comment)
941             {
942             struct message_address *message;
943              
944 107           message = malloc(sizeof(struct message_address));
945 107 50         if (!message)
946 0           i_panic("malloc() failed: %s", strerror(errno));
947              
948 107 100         message->name = name ? strdup(name) : NULL;
949 107 50         message->route = route ? strdup(route) : NULL;
950 107 100         message->mailbox = mailbox ? strdup(mailbox) : NULL;
951 107 100         message->domain = domain ? strdup(domain) : NULL;
952 107 100         message->comment = comment ? strdup(comment) : NULL;
953 107           message->original = NULL;
954 107           message->next = NULL;
955              
956 107 100         if (!*first)
957 49           *first = message;
958             else
959 58           (*last)->next = message;
960              
961 107           *last = message;
962 107           }
963              
964 104           void message_address_free(struct message_address **addr)
965             {
966             struct message_address *current;
967             struct message_address *next;
968              
969 104           current = *addr;
970              
971 289 100         while (current) {
972 185           next = current->next;
973 185           free(current->name);
974 185           free(current->route);
975 185           free(current->mailbox);
976 185           free(current->domain);
977 185           free(current->comment);
978 185           free(current->original);
979 185           free(current);
980 185           current = next;
981             }
982              
983 104           *addr = NULL;
984 104           }
985              
986             struct message_address *
987 41           message_address_parse(const char *input, size_t input_len,
988             unsigned int max_addresses, bool fill_missing)
989             {
990             string_t *str;
991             struct message_address_parser_context ctx;
992              
993 41           memset(&ctx, 0, sizeof(ctx));
994              
995 41           str = str_new(128);
996              
997 41           rfc822_parser_init(&ctx.parser, (const unsigned char *)input, input_len, str);
998              
999 41 100         if (rfc822_skip_lwsp(&ctx.parser) <= 0) {
1000             /* no addresses */
1001 8           str_free(&str);
1002 8           return NULL;
1003             }
1004              
1005 33           ctx.str = str_new(128);
1006 33           ctx.fill_missing = fill_missing;
1007              
1008 33           (void)parse_address_list(&ctx, max_addresses);
1009              
1010 33           str_free(&ctx.str);
1011 33           str_free(&str);
1012              
1013 41           return ctx.first_addr;
1014             }
1015              
1016 63           void message_address_write(char **output, const struct message_address *addr)
1017             {
1018             string_t *str;
1019             const char *tmp;
1020 63           bool first = true, in_group = false;
1021              
1022 63           str = str_new(128);
1023              
1024             /* a) mailbox@domain
1025             b) name <@route:mailbox@domain>
1026             c) group: .. ; */
1027              
1028 170 100         while (addr != NULL) {
1029 107 100         if (first)
1030 69           first = false;
1031             else
1032 38           str_append(str, ", ");
1033              
1034 107 100         if (addr->domain == NULL) {
1035 40 100         if (!in_group) {
1036             /* beginning of group. mailbox is the group
1037             name, others are NULL. */
1038 20 50         if (addr->mailbox != NULL && *addr->mailbox != '\0') {
    100          
1039             /* check for MIME encoded-word */
1040 38 100         if (strstr(addr->mailbox, "=?") != NULL)
1041             /* MIME encoded-word MUST NOT appear within a 'quoted-string'
1042             so escaping and quoting of phrase is not possible, instead
1043             use obsolete RFC822 phrase syntax which allow spaces */
1044 1           str_append(str, addr->mailbox);
1045             else
1046 18           str_append_maybe_escape(str, addr->mailbox, true);
1047             } else {
1048             /* empty group name needs to be quoted */
1049 1           str_append(str, "\"\"");
1050             }
1051 20           str_append(str, ": ");
1052 20           first = true;
1053             } else {
1054             /* end of group. all fields should be NULL. */
1055             i_assert(addr->mailbox == NULL);
1056              
1057             /* cut out the ", " */
1058 20           tmp = str_c(str)+str_len(str)-2;
1059             i_assert((tmp[0] == ',' || tmp[0] == ':') && tmp[1] == ' ');
1060 20 100         if (tmp[0] == ',' && tmp[1] == ' ')
    50          
1061 14           str_truncate(str, str_len(str)-2);
1062 6 50         else if (tmp[0] == ':' && tmp[1] == ' ')
    50          
1063 6           str_truncate(str, str_len(str)-1);
1064 20           str_append_c(str, ';');
1065             }
1066              
1067 40           in_group = !in_group;
1068 67 100         } else if ((addr->name == NULL || *addr->name == '\0') &&
    50          
    50          
1069 19           addr->route == NULL) {
1070             /* no name and no route. use only mailbox@domain */
1071             i_assert(addr->mailbox != NULL);
1072              
1073 19           str_append_maybe_escape(str, addr->mailbox, false);
1074 19           str_append_c(str, '@');
1075 19           str_append(str, addr->domain);
1076              
1077 20 100         if (addr->comment != NULL) {
1078 1           str_append(str, " (");
1079 1           str_append(str, addr->comment);
1080 1           str_append_c(str, ')');
1081             }
1082             } else {
1083             /* name and/or route. use full Name */
1084             i_assert(addr->mailbox != NULL);
1085              
1086 48 50         if (addr->name != NULL && *addr->name != '\0') {
    50          
1087             /* check for MIME encoded-word */
1088 48 100         if (strstr(addr->name, "=?"))
1089             /* MIME encoded-word MUST NOT appear within a 'quoted-string'
1090             so escaping and quoting of phrase is not possible, instead
1091             use obsolete RFC822 phrase syntax which allow spaces */
1092 2           str_append(str, addr->name);
1093             else
1094 46           str_append_maybe_escape(str, addr->name, true);
1095             }
1096 48 50         if (addr->route != NULL ||
    50          
1097 0 0         addr->mailbox[0] != '\0' ||
1098 0           addr->domain[0] != '\0') {
1099 48 50         if (addr->name != NULL && addr->name[0] != '\0')
    50          
1100 48           str_append_c(str, ' ');
1101 48           str_append_c(str, '<');
1102 48 50         if (addr->route != NULL) {
1103 0           str_append(str, addr->route);
1104 0           str_append_c(str, ':');
1105             }
1106 48 50         if (addr->mailbox[0] == '\0')
1107 0           str_append(str, "\"\"");
1108             else
1109 48           str_append_maybe_escape(str, addr->mailbox, false);
1110 48 50         if (addr->domain[0] != '\0') {
1111 48           str_append_c(str, '@');
1112 48           str_append(str, addr->domain);
1113             }
1114 48           str_append_c(str, '>');
1115             }
1116 48 100         if (addr->comment != NULL) {
1117 18           str_append(str, " (");
1118 18           str_append(str, addr->comment);
1119 18           str_append_c(str, ')');
1120             }
1121             }
1122              
1123 107           addr = addr->next;
1124             }
1125              
1126 63           *output = strdup(str_c(str));
1127 63           str_free(&str);
1128 63           }
1129              
1130 93           void compose_address(char **output, const char *mailbox, const char *domain)
1131             {
1132             string_t *str;
1133              
1134 93           str = str_new(128);
1135              
1136 93           str_append_maybe_escape(str, mailbox, false);
1137 93           str_append_c(str, '@');
1138 93           str_append(str, domain);
1139              
1140 93           *output = strdup(str_c(str));
1141 93           str_free(&str);
1142 93           }
1143              
1144 92           void split_address(const char *input, size_t input_len, char **mailbox, char **domain)
1145             {
1146             struct message_address_parser_context ctx;
1147             int ret;
1148              
1149 92 50         if (!input || !input[0]) {
    100          
1150 1           *mailbox = NULL;
1151 1           *domain = NULL;
1152 1           return;
1153             }
1154              
1155 91           memset(&ctx, 0, sizeof(ctx));
1156              
1157 91           rfc822_parser_init(&ctx.parser, (const unsigned char *)input, input_len, NULL);
1158              
1159 91           ctx.str = str_new(128);
1160 91           ctx.fill_missing = false;
1161              
1162 91           ret = rfc822_skip_lwsp(&ctx.parser);
1163              
1164 91 50         if (ret > 0)
1165 91           ret = parse_addr_spec(&ctx);
1166             else
1167 0           ret = -1;
1168              
1169 91 100         if (ret < 0 || ctx.parser.data != ctx.parser.end || ctx.addr.invalid_syntax) {
    100          
    100          
1170 8           free(ctx.addr.mailbox);
1171 8           free(ctx.addr.domain);
1172 8           *mailbox = NULL;
1173 8           *domain = NULL;
1174             } else {
1175 83           *mailbox = ctx.addr.mailbox;
1176 83           *domain = ctx.addr.domain;
1177             }
1178              
1179 91           free(ctx.addr.comment);
1180 91           free(ctx.addr.route);
1181 91           free(ctx.addr.name);
1182 91           free(ctx.addr.original);
1183              
1184 91           str_free(&ctx.str);
1185             }
1186              
1187 340           void string_free(char *string)
1188             {
1189 340           free(string);
1190 340           }