/src/pigeonhole/src/lib-sieve/plugins/enotify/mailto/uri-mailto.c
Line | Count | Source |
1 | | /* Copyright (c) Pigeonhole authors, see top-level COPYING file */ |
2 | | |
3 | | /* FIXME: URI syntax conforms to something somewhere in between RFC 2368 and |
4 | | draft-duerst-mailto-bis-05.txt. Should fully migrate to new |
5 | | specification when it matures. This requires modifications to the |
6 | | address parser (no whitespace allowed within the address itself) and |
7 | | UTF-8 support will be required in the URL. |
8 | | */ |
9 | | |
10 | | #include "lib.h" |
11 | | #include "array.h" |
12 | | #include "str.h" |
13 | | #include "str-sanitize.h" |
14 | | |
15 | | #include "rfc2822.h" |
16 | | |
17 | | #include "sieve-common.h" |
18 | | #include "sieve-error.h" |
19 | | #include "sieve-address.h" |
20 | | #include "sieve-message.h" |
21 | | |
22 | | #include "uri-mailto.h" |
23 | | |
24 | | /* Parser object */ |
25 | | |
26 | | struct uri_mailto_parser { |
27 | | pool_t pool; |
28 | | const struct uri_mailto_log *log; |
29 | | |
30 | | struct uri_mailto *uri; |
31 | | |
32 | | const char **reserved_headers; |
33 | | const char **unique_headers; |
34 | | |
35 | | int max_recipients; |
36 | | int max_headers; |
37 | | }; |
38 | | |
39 | | /* Error handling */ |
40 | | |
41 | | #define uri_mailto_error(PARSER, ...) \ |
42 | 0 | uri_mailto_log((PARSER), LOG_TYPE_ERROR, \ |
43 | 0 | __FILE__, __LINE__, __VA_ARGS__) |
44 | | #define uri_mailto_warning(PARSER, ...) \ |
45 | 0 | uri_mailto_log((PARSER), LOG_TYPE_WARNING, \ |
46 | 0 | __FILE__, __LINE__, __VA_ARGS__) |
47 | | |
48 | | static inline void ATTR_FORMAT(5, 6) |
49 | | uri_mailto_log(struct uri_mailto_parser *parser, enum log_type log_type, |
50 | | const char *csrc_filename, unsigned int csrc_linenum, |
51 | | const char *fmt, ...) |
52 | 0 | { |
53 | 0 | va_list args; |
54 | |
|
55 | 0 | if (parser->log == NULL || parser->log->logv == NULL) |
56 | 0 | return; |
57 | | |
58 | 0 | va_start(args, fmt); |
59 | 0 | parser->log->logv(parser->log->context, log_type, |
60 | 0 | csrc_filename, csrc_linenum, fmt, args); |
61 | 0 | va_end(args); |
62 | 0 | } |
63 | | |
64 | | |
65 | | /* |
66 | | * Error handling |
67 | | */ |
68 | | |
69 | | /* |
70 | | * Reserved and unique headers |
71 | | */ |
72 | | |
73 | | static inline bool |
74 | | uri_mailto_header_is_reserved(struct uri_mailto_parser *parser, |
75 | | const char *field_name) |
76 | 0 | { |
77 | 0 | const char **hdr = parser->reserved_headers; |
78 | |
|
79 | 0 | if (hdr == NULL) |
80 | 0 | return FALSE; |
81 | | |
82 | | /* Check whether it is reserved */ |
83 | 0 | while (*hdr != NULL) { |
84 | 0 | if (strcasecmp(field_name, *hdr) == 0) |
85 | 0 | return TRUE; |
86 | 0 | hdr++; |
87 | 0 | } |
88 | 0 | return FALSE; |
89 | 0 | } |
90 | | |
91 | | static inline bool |
92 | | uri_mailto_header_is_unique(struct uri_mailto_parser *parser, |
93 | | const char *field_name) |
94 | 0 | { |
95 | 0 | const char **hdr = parser->unique_headers; |
96 | |
|
97 | 0 | if (hdr == NULL) |
98 | 0 | return FALSE; |
99 | | |
100 | | /* Check whether it is supposed to be unique */ |
101 | 0 | while (*hdr != NULL) { |
102 | 0 | if (strcasecmp(field_name, *hdr) == 0) |
103 | 0 | return TRUE; |
104 | 0 | hdr++; |
105 | 0 | } |
106 | 0 | return FALSE; |
107 | 0 | } |
108 | | |
109 | | /* |
110 | | * Low-level URI parsing. |
111 | | * |
112 | | * FIXME: much of this implementation will be common to other URI schemes. This |
113 | | * should be merged into a common implementation. |
114 | | */ |
115 | | |
116 | | static const char _qchar_lookup[256] = { |
117 | | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 00 |
118 | | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 10 |
119 | | 0, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 0, // 20 |
120 | | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, // 30 |
121 | | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 40 |
122 | | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, // 50 |
123 | | 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 60 |
124 | | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, // 70 |
125 | | |
126 | | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 80 |
127 | | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 90 |
128 | | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A0 |
129 | | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B0 |
130 | | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // C0 |
131 | | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // D0 |
132 | | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // E0 |
133 | | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // F0 |
134 | | }; |
135 | | |
136 | | static inline bool _is_qchar(char c) |
137 | 0 | { |
138 | 0 | return ((_qchar_lookup[(unsigned char)c] & 0x01) != 0); |
139 | 0 | } |
140 | | |
141 | | static inline int _decode_hex_digit(unsigned char digit) |
142 | 0 | { |
143 | 0 | switch (digit) { |
144 | 0 | case '0': case '1': case '2': case '3': case '4': |
145 | 0 | case '5': case '6': case '7': case '8': case '9': |
146 | 0 | return (int) digit - '0'; |
147 | 0 | case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': |
148 | 0 | return (int) digit - 'a' + 0x0a; |
149 | 0 | case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': |
150 | 0 | return (int) digit - 'A' + 0x0A; |
151 | 0 | } |
152 | 0 | return -1; |
153 | 0 | } |
154 | | |
155 | | static bool _parse_hex_value(const char **in, char *out) |
156 | 0 | { |
157 | 0 | int value, digit; |
158 | |
|
159 | 0 | if ((digit = _decode_hex_digit((unsigned char)**in)) < 0) |
160 | 0 | return FALSE; |
161 | | |
162 | 0 | value = digit << 4; |
163 | 0 | (*in)++; |
164 | |
|
165 | 0 | if ((digit = _decode_hex_digit((unsigned char)**in)) < 0) |
166 | 0 | return FALSE; |
167 | | |
168 | 0 | value |= digit; |
169 | 0 | (*in)++; |
170 | |
|
171 | 0 | if (value == 0) |
172 | 0 | return FALSE; |
173 | | |
174 | 0 | *out = (char)value; |
175 | 0 | return TRUE; |
176 | 0 | } |
177 | | |
178 | | /* |
179 | | * URI recipient parsing |
180 | | */ |
181 | | |
182 | | static bool |
183 | | uri_mailto_add_valid_recipient(struct uri_mailto_parser *parser, |
184 | | string_t *recipient, bool cc) |
185 | 0 | { |
186 | 0 | struct uri_mailto *uri = parser->uri; |
187 | 0 | struct uri_mailto_recipient *new_recipient; |
188 | 0 | struct uri_mailto_recipient *rcpts; |
189 | 0 | unsigned int count, i; |
190 | 0 | const char *error; |
191 | 0 | const struct smtp_address *address; |
192 | | |
193 | | /* Verify recipient */ |
194 | 0 | if ((address = sieve_address_parse_str(recipient, &error)) == NULL) { |
195 | 0 | uri_mailto_error(parser, "invalid recipient '%s': %s", |
196 | 0 | str_sanitize(str_c(recipient), 80), error); |
197 | 0 | return FALSE; |
198 | 0 | } |
199 | | |
200 | | /* Add recipient to the uri */ |
201 | 0 | if (uri != NULL) { |
202 | | /* Get current recipients */ |
203 | 0 | rcpts = array_get_modifiable(&uri->recipients, &count); |
204 | | |
205 | | /* Enforce limits */ |
206 | 0 | if (parser->max_recipients > 0 && |
207 | 0 | (int)count >= parser->max_recipients) { |
208 | 0 | if ((int)count == parser->max_recipients) { |
209 | 0 | uri_mailto_warning( |
210 | 0 | parser, |
211 | 0 | "more than the maximum %u recipients specified; " |
212 | 0 | "rest is discarded", |
213 | 0 | parser->max_recipients); |
214 | 0 | } |
215 | 0 | return TRUE; |
216 | 0 | } |
217 | | |
218 | | /* Check for duplicate first */ |
219 | 0 | for (i = 0; i < count; i++) { |
220 | 0 | if (smtp_address_equals(rcpts[i].address, address)) { |
221 | | /* Upgrade existing Cc: recipient to a To: |
222 | | recipient if possible */ |
223 | 0 | rcpts[i].carbon_copy = |
224 | 0 | (rcpts[i].carbon_copy && cc); |
225 | |
|
226 | 0 | uri_mailto_warning( |
227 | 0 | parser, |
228 | 0 | "ignored duplicate recipient '%s'", |
229 | 0 | str_sanitize(str_c(recipient), 80)); |
230 | 0 | return TRUE; |
231 | 0 | } |
232 | 0 | } |
233 | | |
234 | | /* Add */ |
235 | 0 | new_recipient = array_append_space(&uri->recipients); |
236 | 0 | new_recipient->carbon_copy = cc; |
237 | 0 | new_recipient->full = p_strdup(parser->pool, str_c(recipient)); |
238 | 0 | new_recipient->address = |
239 | 0 | smtp_address_clone(parser->pool, address); |
240 | 0 | } |
241 | | |
242 | 0 | return TRUE; |
243 | 0 | } |
244 | | |
245 | | static bool |
246 | | uri_mailto_parse_recipients(struct uri_mailto_parser *parser, |
247 | | const char **uri_p) |
248 | 0 | { |
249 | 0 | string_t *to = t_str_new(128); |
250 | 0 | const char *p = *uri_p; |
251 | |
|
252 | 0 | if (*p == '\0' || *p == '?') |
253 | 0 | return TRUE; |
254 | | |
255 | 0 | while (*p != '\0' && *p != '?') { |
256 | 0 | if (*p == '%') { |
257 | | /* % encoded character */ |
258 | 0 | char ch; |
259 | |
|
260 | 0 | p++; |
261 | | |
262 | | /* Parse 2-digit hex value */ |
263 | 0 | if (!_parse_hex_value(&p, &ch)) { |
264 | 0 | uri_mailto_error(parser, "invalid %% encoding"); |
265 | 0 | return FALSE; |
266 | 0 | } |
267 | | |
268 | | /* Check for delimiter */ |
269 | 0 | if (ch == ',') { |
270 | | /* Verify and add recipient */ |
271 | 0 | if (!uri_mailto_add_valid_recipient( |
272 | 0 | parser, to, FALSE)) |
273 | 0 | return FALSE; |
274 | | |
275 | | /* Reset for next recipient */ |
276 | 0 | str_truncate(to, 0); |
277 | 0 | } else { |
278 | | /* Content character */ |
279 | 0 | str_append_c(to, ch); |
280 | 0 | } |
281 | 0 | } else { |
282 | 0 | if (*p == ':' || *p == ';' || *p == ',' || |
283 | 0 | !_is_qchar(*p)) { |
284 | 0 | uri_mailto_error( |
285 | 0 | parser, |
286 | 0 | "invalid character '%c' in 'to' part", *p); |
287 | 0 | return FALSE; |
288 | 0 | } |
289 | | |
290 | | /* Content character */ |
291 | 0 | str_append_c(to, *p); |
292 | 0 | p++; |
293 | 0 | } |
294 | 0 | } |
295 | | |
296 | 0 | i_assert(*p == '\0' || *p == '?'); |
297 | | |
298 | | /* Verify and add recipient */ |
299 | 0 | if (!uri_mailto_add_valid_recipient(parser, to, FALSE)) |
300 | 0 | return FALSE; |
301 | | |
302 | 0 | *uri_p = p; |
303 | 0 | return TRUE; |
304 | 0 | } |
305 | | |
306 | | static bool |
307 | | uri_mailto_parse_header_recipients(struct uri_mailto_parser *parser, |
308 | | string_t *rcpt_header, bool cc) |
309 | 0 | { |
310 | 0 | string_t *to = t_str_new(128); |
311 | 0 | const char *p = (const char *)str_data(rcpt_header); |
312 | 0 | const char *pend = p + str_len(rcpt_header); |
313 | |
|
314 | 0 | while (p < pend) { |
315 | 0 | if (*p == ',') { |
316 | | /* Verify and add recipient */ |
317 | 0 | if (!uri_mailto_add_valid_recipient(parser, to, cc)) |
318 | 0 | return FALSE; |
319 | | |
320 | | /* Reset for next recipient */ |
321 | 0 | str_truncate(to, 0); |
322 | 0 | } else { |
323 | | /* Content character */ |
324 | 0 | str_append_c(to, *p); |
325 | 0 | } |
326 | 0 | p++; |
327 | 0 | } |
328 | | |
329 | | /* Verify and add recipient */ |
330 | 0 | if (!uri_mailto_add_valid_recipient(parser, to, cc)) |
331 | 0 | return FALSE; |
332 | | |
333 | 0 | return TRUE; |
334 | 0 | } |
335 | | |
336 | | /* URI header parsing */ |
337 | | |
338 | | static bool |
339 | | uri_mailto_header_is_duplicate(struct uri_mailto_parser *parser, |
340 | | const char *field_name) |
341 | 0 | { |
342 | 0 | struct uri_mailto *uri = parser->uri; |
343 | |
|
344 | 0 | if (uri == NULL) |
345 | 0 | return FALSE; |
346 | | |
347 | 0 | if (uri_mailto_header_is_unique(parser, field_name)) { |
348 | 0 | const struct uri_mailto_header_field *hdrs; |
349 | 0 | unsigned int count, i; |
350 | |
|
351 | 0 | hdrs = array_get(&uri->headers, &count); |
352 | 0 | for (i = 0; i < count; i++) { |
353 | 0 | if (strcasecmp(hdrs[i].name, field_name) == 0) |
354 | 0 | return TRUE; |
355 | 0 | } |
356 | 0 | } |
357 | 0 | return FALSE; |
358 | 0 | } |
359 | | |
360 | | static bool |
361 | | uri_mailto_parse_headers(struct uri_mailto_parser *parser, const char **uri_p) |
362 | 0 | { |
363 | 0 | struct uri_mailto *uri = parser->uri; |
364 | 0 | unsigned int header_count = 0; |
365 | 0 | string_t *field = t_str_new(128); |
366 | 0 | const char *p = *uri_p; |
367 | |
|
368 | 0 | while (*p != '\0') { |
369 | 0 | enum { |
370 | 0 | _HNAME_IGNORED, |
371 | 0 | _HNAME_GENERIC, |
372 | 0 | _HNAME_TO, |
373 | 0 | _HNAME_CC, |
374 | 0 | _HNAME_SUBJECT, |
375 | 0 | _HNAME_BODY, |
376 | 0 | } hname_type = _HNAME_GENERIC; |
377 | 0 | struct uri_mailto_header_field *hdrf = NULL; |
378 | 0 | const char *field_name; |
379 | | |
380 | | /* Parse field name */ |
381 | 0 | while (*p != '\0' && *p != '=') { |
382 | 0 | char ch = *p; |
383 | 0 | p++; |
384 | |
|
385 | 0 | if (ch == '%') { |
386 | | /* Encoded, parse 2-digit hex value */ |
387 | 0 | if (!_parse_hex_value(&p, &ch)) { |
388 | 0 | uri_mailto_error(parser, |
389 | 0 | "invalid %% encoding"); |
390 | 0 | return FALSE; |
391 | 0 | } |
392 | 0 | } else if (ch != '=' && !_is_qchar(ch)) { |
393 | 0 | uri_mailto_error( |
394 | 0 | parser, |
395 | 0 | "invalid character '%c' in header field name part", |
396 | 0 | ch); |
397 | 0 | return FALSE; |
398 | 0 | } |
399 | | |
400 | 0 | str_append_c(field, ch); |
401 | 0 | } |
402 | 0 | if (*p != '\0') |
403 | 0 | p++; |
404 | | |
405 | | /* Verify field name */ |
406 | 0 | if (!rfc2822_header_field_name_verify(str_c(field), |
407 | 0 | str_len(field))) { |
408 | 0 | uri_mailto_error(parser, "invalid header field name"); |
409 | 0 | return FALSE; |
410 | 0 | } |
411 | | |
412 | 0 | if (parser->max_headers > -1 && |
413 | 0 | (int)header_count >= parser->max_headers) { |
414 | | /* Refuse to accept more headers than allowed by policy |
415 | | */ |
416 | 0 | if ((int)header_count == parser->max_headers) { |
417 | 0 | uri_mailto_warning( |
418 | 0 | parser, |
419 | 0 | "more than the maximum %u headers specified; " |
420 | 0 | "rest is discarded", |
421 | 0 | parser->max_headers); |
422 | 0 | } |
423 | |
|
424 | 0 | hname_type = _HNAME_IGNORED; |
425 | 0 | } else { |
426 | | /* Add new header field to array and assign its name */ |
427 | |
|
428 | 0 | field_name = str_c(field); |
429 | 0 | if (strcasecmp(field_name, "to") == 0) |
430 | 0 | hname_type = _HNAME_TO; |
431 | 0 | else if (strcasecmp(field_name, "cc") == 0) |
432 | 0 | hname_type = _HNAME_CC; |
433 | 0 | else if (strcasecmp(field_name, "subject") == 0) |
434 | 0 | hname_type = _HNAME_SUBJECT; |
435 | 0 | else if (strcasecmp(field_name, "body") == 0) |
436 | 0 | hname_type = _HNAME_BODY; |
437 | 0 | else if (!uri_mailto_header_is_reserved(parser, field_name)) { |
438 | 0 | if (uri != NULL) { |
439 | 0 | if (!uri_mailto_header_is_duplicate(parser, field_name)) { |
440 | 0 | hdrf = array_append_space(&uri->headers); |
441 | 0 | hdrf->name = p_strdup(parser->pool, field_name); |
442 | 0 | } else { |
443 | 0 | uri_mailto_warning( |
444 | 0 | parser, |
445 | 0 | "ignored duplicate for unique header field '%s'", |
446 | 0 | str_sanitize(field_name, 32)); |
447 | 0 | hname_type = _HNAME_IGNORED; |
448 | 0 | } |
449 | 0 | } else { |
450 | 0 | hname_type = _HNAME_IGNORED; |
451 | 0 | } |
452 | 0 | } else { |
453 | 0 | uri_mailto_warning( |
454 | 0 | parser, |
455 | 0 | "ignored reserved header field '%s'", |
456 | 0 | str_sanitize(field_name, 32)); |
457 | 0 | hname_type = _HNAME_IGNORED; |
458 | 0 | } |
459 | 0 | } |
460 | |
|
461 | 0 | header_count++; |
462 | | |
463 | | /* Reset for field body */ |
464 | 0 | str_truncate(field, 0); |
465 | | |
466 | | /* Parse field body */ |
467 | 0 | while (*p != '\0' && *p != '&') { |
468 | 0 | char ch = *p; |
469 | 0 | p++; |
470 | |
|
471 | 0 | if (ch == '%') { |
472 | | /* Encoded, parse 2-digit hex value */ |
473 | 0 | if (!_parse_hex_value(&p, &ch)) { |
474 | 0 | uri_mailto_error(parser, |
475 | 0 | "invalid %% encoding"); |
476 | 0 | return FALSE; |
477 | 0 | } |
478 | 0 | } else if (ch != '=' && !_is_qchar(ch)) { |
479 | 0 | uri_mailto_error( |
480 | 0 | parser, |
481 | 0 | "invalid character '%c' in header field value part", |
482 | 0 | ch); |
483 | 0 | return FALSE; |
484 | 0 | } |
485 | 0 | str_append_c(field, ch); |
486 | 0 | } |
487 | 0 | if (*p != '\0') |
488 | 0 | p++; |
489 | | |
490 | | /* Verify field body */ |
491 | 0 | if (hname_type == _HNAME_BODY) { |
492 | | // FIXME: verify body ... |
493 | 0 | } else { |
494 | 0 | if (!rfc2822_header_field_body_verify( |
495 | 0 | str_c(field), str_len(field), FALSE, FALSE)) { |
496 | 0 | uri_mailto_error(parser, |
497 | 0 | "invalid header field body"); |
498 | 0 | return FALSE; |
499 | 0 | } |
500 | 0 | } |
501 | | |
502 | | /* Assign field body */ |
503 | | |
504 | 0 | switch (hname_type) { |
505 | 0 | case _HNAME_IGNORED: |
506 | 0 | break; |
507 | 0 | case _HNAME_TO: |
508 | | /* Gracefully allow duplicate To fields */ |
509 | 0 | if (!uri_mailto_parse_header_recipients( |
510 | 0 | parser, field, FALSE)) |
511 | 0 | return FALSE; |
512 | 0 | break; |
513 | 0 | case _HNAME_CC: |
514 | | /* Gracefully allow duplicate Cc fields */ |
515 | 0 | if (!uri_mailto_parse_header_recipients( |
516 | 0 | parser, field, TRUE)) |
517 | 0 | return FALSE; |
518 | 0 | break; |
519 | 0 | case _HNAME_SUBJECT: |
520 | | /* Ignore duplicate subject field */ |
521 | 0 | if (uri != NULL) { |
522 | 0 | if (uri->subject == NULL) { |
523 | 0 | uri->subject = |
524 | 0 | p_strdup(parser->pool, str_c(field)); |
525 | 0 | } else { |
526 | 0 | uri_mailto_warning( |
527 | 0 | parser, |
528 | 0 | "ignored duplicate subject field"); |
529 | 0 | } |
530 | 0 | } |
531 | 0 | break; |
532 | 0 | case _HNAME_BODY: |
533 | | /* Ignore duplicate body field */ |
534 | 0 | if (uri != NULL) { |
535 | 0 | if (uri->body == NULL) { |
536 | 0 | uri->body = p_strdup( |
537 | 0 | parser->pool, str_c(field)); |
538 | 0 | } else { |
539 | 0 | uri_mailto_warning( |
540 | 0 | parser, "ignored duplicate body field"); |
541 | 0 | } |
542 | 0 | } |
543 | 0 | break; |
544 | 0 | case _HNAME_GENERIC: |
545 | 0 | if (uri != NULL && hdrf != NULL) |
546 | 0 | hdrf->body = p_strdup(parser->pool, str_c(field)); |
547 | 0 | break; |
548 | 0 | } |
549 | | |
550 | | /* Reset for next name */ |
551 | 0 | str_truncate(field, 0); |
552 | 0 | } |
553 | | |
554 | | /* Skip '&' */ |
555 | 0 | if (*p != '\0') |
556 | 0 | p++; |
557 | |
|
558 | 0 | *uri_p = p; |
559 | 0 | return TRUE; |
560 | 0 | } |
561 | | |
562 | | static bool |
563 | | uri_mailto_parse_uri(struct uri_mailto_parser *parser, const char *uri_body) |
564 | 0 | { |
565 | 0 | const char *p = uri_body; |
566 | | |
567 | | /* |
568 | | * mailtoURI = "mailto:" [ to ] [ hfields ] |
569 | | * to = [ addr-spec *("%2C" addr-spec ) ] |
570 | | * hfields = "?" hfield *( "&" hfield ) |
571 | | * hfield = hfname "=" hfvalue |
572 | | * hfname = *qchar |
573 | | * hfvalue = *qchar |
574 | | * addr-spec = local-part "@" domain |
575 | | * local-part = dot-atom / quoted-string |
576 | | * qchar = unreserved / pct-encoded / some-delims |
577 | | * some-delims = "!" / "$" / "'" / "(" / ")" / "*" |
578 | | * / "+" / "," / ";" / ":" / "@" |
579 | | * |
580 | | * to ~= *tqchar |
581 | | * tqchar ~= <qchar> without ";" and ":" |
582 | | * |
583 | | * Scheme 'mailto:' already parsed, starting parse after colon |
584 | | */ |
585 | | |
586 | | /* First extract to-part by searching for '?' and decoding % items |
587 | | */ |
588 | |
|
589 | 0 | if (!uri_mailto_parse_recipients(parser, &p)) |
590 | 0 | return FALSE; |
591 | | |
592 | 0 | if (*p == '\0') |
593 | 0 | return TRUE; |
594 | 0 | i_assert(*p == '?'); |
595 | 0 | p++; |
596 | | |
597 | | /* Extract hfield items */ |
598 | |
|
599 | 0 | while (*p != '\0') { |
600 | | /* Extract hfield item by searching for '&' and decoding '%' |
601 | | items */ |
602 | 0 | if (!uri_mailto_parse_headers(parser, &p)) |
603 | 0 | return FALSE; |
604 | 0 | } |
605 | 0 | return TRUE; |
606 | 0 | } |
607 | | |
608 | | /* |
609 | | * Validation |
610 | | */ |
611 | | |
612 | | bool uri_mailto_validate(const char *uri_body, |
613 | | const char **reserved_headers, |
614 | | const char **unique_headers, int max_recipients, |
615 | | int max_headers, const struct uri_mailto_log *log) |
616 | 0 | { |
617 | 0 | struct uri_mailto_parser parser; |
618 | |
|
619 | 0 | i_zero(&parser); |
620 | 0 | parser.log = log; |
621 | 0 | parser.max_recipients = max_recipients; |
622 | 0 | parser.max_headers = max_headers; |
623 | 0 | parser.reserved_headers = reserved_headers; |
624 | 0 | parser.unique_headers = unique_headers; |
625 | | |
626 | | /* If no errors are reported, we don't need to record any data */ |
627 | 0 | if (log != NULL) { |
628 | 0 | parser.pool = pool_datastack_create(); |
629 | |
|
630 | 0 | parser.uri = p_new(parser.pool, struct uri_mailto, 1); |
631 | 0 | p_array_init(&parser.uri->recipients, parser.pool, max_recipients); |
632 | 0 | p_array_init(&parser.uri->headers, parser.pool, max_headers); |
633 | 0 | } |
634 | |
|
635 | 0 | if (!uri_mailto_parse_uri(&parser, uri_body)) |
636 | 0 | return FALSE; |
637 | | |
638 | 0 | if (log != NULL) { |
639 | 0 | if (array_count(&parser.uri->recipients) == 0) { |
640 | 0 | uri_mailto_warning( |
641 | 0 | &parser, |
642 | 0 | "notification URI specifies no recipients"); |
643 | 0 | } |
644 | 0 | } |
645 | 0 | return TRUE; |
646 | 0 | } |
647 | | |
648 | | /* |
649 | | * Parsing |
650 | | */ |
651 | | |
652 | | struct uri_mailto * |
653 | | uri_mailto_parse(const char *uri_body, pool_t pool, |
654 | | const char **reserved_headers, const char **unique_headers, |
655 | | int max_recipients, int max_headers, |
656 | | const struct uri_mailto_log *log) |
657 | 0 | { |
658 | 0 | struct uri_mailto_parser parser; |
659 | |
|
660 | 0 | parser.pool = pool; |
661 | 0 | parser.log = log; |
662 | 0 | parser.max_recipients = max_recipients; |
663 | 0 | parser.max_headers = max_headers; |
664 | 0 | parser.reserved_headers = reserved_headers; |
665 | 0 | parser.unique_headers = unique_headers; |
666 | |
|
667 | 0 | parser.uri = p_new(pool, struct uri_mailto, 1); |
668 | 0 | p_array_init(&parser.uri->recipients, pool, max_recipients); |
669 | 0 | p_array_init(&parser.uri->headers, pool, max_headers); |
670 | |
|
671 | 0 | if (!uri_mailto_parse_uri(&parser, uri_body)) |
672 | 0 | return NULL; |
673 | | |
674 | 0 | if (log != NULL) { |
675 | 0 | if (array_count(&parser.uri->recipients) == 0) { |
676 | 0 | uri_mailto_warning( |
677 | 0 | &parser, |
678 | 0 | "notification URI specifies no recipients"); |
679 | 0 | } |
680 | 0 | } |
681 | 0 | return parser.uri; |
682 | 0 | } |