1 /*
2 * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com)
3 *
4 * This library is free software: you can redistribute it and/or modify it
5 * under the terms of the GNU Lesser General Public License as published by
6 * the Free Software Foundation.
7 *
8 * This library is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
10 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
11 * for more details.
12 *
13 * You should have received a copy of the GNU Lesser General Public License
14 * along with this library. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * Authors: Michael Zucchi <notzed@ximian.com>
17 */
18
19 #include <stdio.h>
20 #include <string.h>
21
22 #include "camel-internet-address.h"
23 #include "camel-hostname-utils.h"
24 #include "camel-mime-utils.h"
25 #include "camel-net-utils.h"
26
27 #define d(x)
28
29 struct _CamelInternetAddressPrivate {
30 GPtrArray *addresses;
31 };
32
33 struct _address {
34 gchar *name;
35 gchar *address;
36 };
37
G_DEFINE_TYPE_WITH_PRIVATE(CamelInternetAddress,camel_internet_address,CAMEL_TYPE_ADDRESS)38 G_DEFINE_TYPE_WITH_PRIVATE (CamelInternetAddress, camel_internet_address, CAMEL_TYPE_ADDRESS)
39
40 static gint
41 internet_address_length (CamelAddress *paddr)
42 {
43 CamelInternetAddress *inet_addr = CAMEL_INTERNET_ADDRESS (paddr);
44
45 g_return_val_if_fail (inet_addr != NULL, -1);
46
47 return inet_addr->priv->addresses->len;
48 }
49
50 static gint
internet_address_decode(CamelAddress * addr,const gchar * raw)51 internet_address_decode (CamelAddress *addr,
52 const gchar *raw)
53 {
54 CamelHeaderAddress *ha, *n;
55 gint count = camel_address_length (addr);
56
57 /* Should probably use its own decoder or something */
58 ha = camel_header_address_decode (raw, NULL);
59 if (ha) {
60 n = ha;
61 while (n) {
62 if (n->type == CAMEL_HEADER_ADDRESS_NAME) {
63 camel_internet_address_add ((CamelInternetAddress *) addr, n->name, n->v.addr);
64 } else if (n->type == CAMEL_HEADER_ADDRESS_GROUP) {
65 CamelHeaderAddress *g = n->v.members;
66 while (g) {
67 if (g->type == CAMEL_HEADER_ADDRESS_NAME)
68 camel_internet_address_add ((CamelInternetAddress *) addr, g->name, g->v.addr);
69 /* otherwise, it's an error, infact */
70 g = g->next;
71 }
72 }
73 n = n->next;
74 }
75 camel_header_address_list_clear (&ha);
76 }
77
78 return camel_address_length (addr) - count;
79 }
80
81 static gchar *
internet_address_encode(CamelAddress * paddr)82 internet_address_encode (CamelAddress *paddr)
83 {
84 CamelInternetAddress *inet_addr = CAMEL_INTERNET_ADDRESS (paddr);
85 gint i;
86 GString *out;
87 gint len = 6; /* "From: ", assume longer of the address headers */
88
89 if (inet_addr->priv->addresses->len == 0)
90 return NULL;
91
92 out = g_string_new ("");
93
94 for (i = 0; i < inet_addr->priv->addresses->len; i++) {
95 struct _address *addr = g_ptr_array_index (inet_addr->priv->addresses, i);
96 gchar *enc;
97
98 if (i != 0)
99 g_string_append (out, ", ");
100
101 enc = camel_internet_address_encode_address (&len, addr->name, addr->address);
102 g_string_append (out, enc);
103 g_free (enc);
104 }
105
106 return g_string_free (out, FALSE);
107 }
108
109 static gint
internet_address_unformat(CamelAddress * paddr,const gchar * raw)110 internet_address_unformat (CamelAddress *paddr,
111 const gchar *raw)
112 {
113 CamelInternetAddress *inet_addr = CAMEL_INTERNET_ADDRESS (paddr);
114 gchar *buffer, *p, *name, *addr;
115 gint c;
116 gint count = inet_addr->priv->addresses->len;
117
118 if (raw == NULL)
119 return 0;
120
121 d (printf ("unformatting address: %s\n", raw));
122
123 /* we copy, so we can modify as we go */
124 buffer = g_strdup (raw);
125
126 /* this can be simpler than decode, since there are much fewer rules */
127 p = buffer;
128 name = NULL;
129 addr = p;
130 do {
131 c = (guchar) * p++;
132 switch (c) {
133 /* removes quotes, they should only be around the total name anyway */
134 case '"':
135 p[-1] = ' ';
136 while (*p)
137 if (*p == '"') {
138 *p++ = ' ';
139 break;
140 } else {
141 p++;
142 }
143 break;
144 case '<':
145 if (name == NULL)
146 name = addr;
147 addr = p;
148 addr[-1] = 0;
149 while (*p && *p != '>')
150 p++;
151 if (*p == 0)
152 break;
153 p++;
154 /* falls through */
155 case ',':
156 p[-1] = 0;
157 /* falls through */
158 case 0:
159 if (name)
160 name = g_strstrip (name);
161 addr = g_strstrip (addr);
162 if (addr[0]) {
163 d (printf ("found address: '%s' <%s>\n", name, addr));
164 camel_internet_address_add (inet_addr, name, addr);
165 }
166 name = NULL;
167 addr = p;
168 break;
169 }
170 } while (c);
171
172 g_free (buffer);
173
174 return inet_addr->priv->addresses->len - count;
175 }
176
177 static gchar *
internet_address_format(CamelAddress * paddr)178 internet_address_format (CamelAddress *paddr)
179 {
180 CamelInternetAddress *inet_addr = CAMEL_INTERNET_ADDRESS (paddr);
181 gint i;
182 GString *out;
183
184 if (inet_addr->priv->addresses->len == 0)
185 return NULL;
186
187 out = g_string_new ("");
188
189 for (i = 0; i < inet_addr->priv->addresses->len; i++) {
190 struct _address *addr = g_ptr_array_index (inet_addr->priv->addresses, i);
191 gchar *enc;
192
193 if (i != 0)
194 g_string_append (out, ", ");
195
196 enc = camel_internet_address_format_address (addr->name, addr->address);
197 g_string_append (out, enc);
198 g_free (enc);
199 }
200
201 return g_string_free (out, FALSE);
202 }
203
204 static void
internet_address_remove(CamelAddress * paddr,gint index)205 internet_address_remove (CamelAddress *paddr,
206 gint index)
207 {
208 CamelInternetAddress *inet_addr = CAMEL_INTERNET_ADDRESS (paddr);
209 struct _address *addr;
210
211 if (index < 0 || index >= inet_addr->priv->addresses->len)
212 return;
213
214 addr = g_ptr_array_index (inet_addr->priv->addresses, index);
215 g_free (addr->name);
216 g_free (addr->address);
217 g_free (addr);
218 g_ptr_array_remove_index (inet_addr->priv->addresses, index);
219 }
220
221 static gint
internet_address_cat(CamelAddress * dest,CamelAddress * source)222 internet_address_cat (CamelAddress *dest,
223 CamelAddress *source)
224 {
225 CamelInternetAddress *dest_inet_addr;
226 CamelInternetAddress *source_inet_addr;
227 gint i;
228
229 g_return_val_if_fail (CAMEL_IS_INTERNET_ADDRESS (dest), -1);
230 g_return_val_if_fail (CAMEL_IS_INTERNET_ADDRESS (source), -1);
231
232 dest_inet_addr = CAMEL_INTERNET_ADDRESS (dest);
233 source_inet_addr = CAMEL_INTERNET_ADDRESS (source);
234
235 for (i = 0; i < source_inet_addr->priv->addresses->len; i++) {
236 struct _address *addr = g_ptr_array_index (source_inet_addr->priv->addresses, i);
237 camel_internet_address_add (dest_inet_addr, addr->name, addr->address);
238 }
239
240 return i;
241 }
242
243 static void
internet_address_finalize(GObject * object)244 internet_address_finalize (GObject *object)
245 {
246 CamelInternetAddress *inet_addr = CAMEL_INTERNET_ADDRESS (object);
247
248 camel_address_remove (CAMEL_ADDRESS (inet_addr), -1);
249 g_ptr_array_free (inet_addr->priv->addresses, TRUE);
250
251 /* Chain up to parent's finalize() method. */
252 G_OBJECT_CLASS (camel_internet_address_parent_class)->finalize (object);
253 }
254
255 static void
camel_internet_address_class_init(CamelInternetAddressClass * class)256 camel_internet_address_class_init (CamelInternetAddressClass *class)
257 {
258 CamelAddressClass *address_class;
259 GObjectClass *object_class;
260
261 object_class = G_OBJECT_CLASS (class);
262 object_class->finalize = internet_address_finalize;
263
264 address_class = CAMEL_ADDRESS_CLASS (class);
265 address_class->length = internet_address_length;
266 address_class->decode = internet_address_decode;
267 address_class->encode = internet_address_encode;
268 address_class->unformat = internet_address_unformat;
269 address_class->format = internet_address_format;
270 address_class->remove = internet_address_remove;
271 address_class->cat = internet_address_cat;
272 }
273
274 static void
camel_internet_address_init(CamelInternetAddress * internet_address)275 camel_internet_address_init (CamelInternetAddress *internet_address)
276 {
277 internet_address->priv = camel_internet_address_get_instance_private (internet_address);
278 internet_address->priv->addresses = g_ptr_array_new ();
279 }
280
281 /**
282 * camel_internet_address_new:
283 *
284 * Create a new #CamelInternetAddress object.
285 *
286 * Returns: a new #CamelInternetAddress object
287 **/
288 CamelInternetAddress *
camel_internet_address_new(void)289 camel_internet_address_new (void)
290 {
291 return g_object_new (CAMEL_TYPE_INTERNET_ADDRESS, NULL);
292 }
293
294 /**
295 * camel_internet_address_add:
296 * @addr: a #CamelInternetAddress object
297 * @name: name associated with the new address
298 * @address: routing address associated with the new address
299 *
300 * Add a new internet address to @addr.
301 *
302 * Returns: the index of added entry
303 **/
304 gint
camel_internet_address_add(CamelInternetAddress * addr,const gchar * name,const gchar * address)305 camel_internet_address_add (CamelInternetAddress *addr,
306 const gchar *name,
307 const gchar *address)
308 {
309 struct _address *new;
310 gint index;
311
312 g_return_val_if_fail (CAMEL_IS_INTERNET_ADDRESS (addr), -1);
313
314 new = g_malloc (sizeof (*new));
315 new->name = g_strdup (name);
316 new->address = g_strdup (address);
317 index = addr->priv->addresses->len;
318 g_ptr_array_add (addr->priv->addresses, new);
319
320 return index;
321 }
322
323 /**
324 * camel_internet_address_get:
325 * @addr: a #CamelInternetAddress object
326 * @index: address's array index
327 * @namep: (out) (optional) (nullable) (transfer none): holder for the returned name, or %NULL, if not required.
328 * @addressp: (out) (optional) (nullable) (transfer none): holder for the returned address, or %NULL, if not required.
329 *
330 * Get the address at @index.
331 *
332 * Returns: %TRUE if such an address exists, or %FALSE otherwise
333 **/
334 gboolean
camel_internet_address_get(CamelInternetAddress * addr,gint index,const gchar ** namep,const gchar ** addressp)335 camel_internet_address_get (CamelInternetAddress *addr,
336 gint index,
337 const gchar **namep,
338 const gchar **addressp)
339 {
340 struct _address *a;
341
342 g_return_val_if_fail (CAMEL_IS_INTERNET_ADDRESS (addr), FALSE);
343
344 if (index < 0 || index >= addr->priv->addresses->len)
345 return FALSE;
346
347 a = g_ptr_array_index (addr->priv->addresses, index);
348 if (namep)
349 *namep = a->name;
350 if (addressp)
351 *addressp = a->address;
352 return TRUE;
353 }
354
355 /**
356 * camel_internet_address_find_name:
357 * @addr: a #CamelInternetAddress object
358 * @name: name to lookup
359 * @addressp: (out) (optional) (nullable) (transfer none): holder for address part, or %NULL, if not required.
360 *
361 * Find address by real name.
362 *
363 * Returns: the index of the address matching the name, or -1 if no
364 * match was found
365 **/
366 gint
camel_internet_address_find_name(CamelInternetAddress * addr,const gchar * name,const gchar ** addressp)367 camel_internet_address_find_name (CamelInternetAddress *addr,
368 const gchar *name,
369 const gchar **addressp)
370 {
371 struct _address *a;
372 gboolean name_is_utf8_valid;
373 gint i, len;
374
375 g_return_val_if_fail (CAMEL_IS_INTERNET_ADDRESS (addr), -1);
376
377 if (!name)
378 return -1;
379
380 name_is_utf8_valid = g_utf8_validate (name, -1, NULL);
381
382 len = addr->priv->addresses->len;
383 for (i = 0; i < len; i++) {
384 gboolean match;
385
386 a = g_ptr_array_index (addr->priv->addresses, i);
387
388 if (!a->name)
389 continue;
390
391 if (name_is_utf8_valid && g_utf8_validate (a->name, -1, NULL))
392 match = !g_utf8_collate (a->name, name);
393 else
394 match = !g_ascii_strcasecmp (a->name, name);
395
396 if (match) {
397 if (addressp)
398 *addressp = a->address;
399 return i;
400 }
401 }
402 return -1;
403 }
404
405 static gboolean
domain_contains_only_ascii(const gchar * address,gint * at_pos)406 domain_contains_only_ascii (const gchar *address,
407 gint *at_pos)
408 {
409 gint pos;
410 gboolean all_ascii = TRUE;
411
412 g_return_val_if_fail (address != NULL, TRUE);
413 g_return_val_if_fail (at_pos != NULL, TRUE);
414
415 *at_pos = -1;
416 for (pos = 0; address[pos]; pos++) {
417 all_ascii = all_ascii && address[pos] > 0;
418 if (*at_pos == -1 && address[pos] == '@') {
419 *at_pos = pos;
420 all_ascii = TRUE;
421 }
422 }
423
424 /* Do not change anything when there is no domain part
425 of the email address */
426 return all_ascii || *at_pos == -1;
427 }
428
429 /**
430 * camel_internet_address_ensure_ascii_domains:
431 * @addr: a #CamelInternetAddress
432 *
433 * Ensures that all email address' domains will be ASCII encoded,
434 * which means that any non-ASCII letters will be properly encoded.
435 * This includes IDN (Internationalized Domain Names).
436 *
437 * Since: 3.16
438 **/
439 void
camel_internet_address_ensure_ascii_domains(CamelInternetAddress * addr)440 camel_internet_address_ensure_ascii_domains (CamelInternetAddress *addr)
441 {
442 struct _address *a;
443 gint i, len;
444
445 g_return_if_fail (CAMEL_IS_INTERNET_ADDRESS (addr));
446
447 len = addr->priv->addresses->len;
448 for (i = 0; i < len; i++) {
449 gint at_pos = -1;
450 a = g_ptr_array_index (addr->priv->addresses, i);
451 if (a->address && !domain_contains_only_ascii (a->address, &at_pos)) {
452 gchar *address, *domain;
453
454 domain = camel_host_idna_to_ascii (a->address + at_pos + 1);
455 if (at_pos >= 0) {
456 address = g_strdup_printf ("%.*s@%s", at_pos, a->address, domain);
457 } else {
458 address = domain;
459 domain = NULL;
460 }
461
462 g_free (domain);
463 g_free (a->address);
464 a->address = address;
465 }
466 }
467 }
468
469 /**
470 * camel_internet_address_sanitize_ascii_domain:
471 * @addr: a #CamelInternetAddress
472 *
473 * Checks the addresses in @addr for any suspicious characters in the domain
474 * name and coverts those domains into their representation. In contrast to
475 * camel_internet_address_ensure_ascii_domains(), this converts the domains
476 * into ASCII only when needed, as returned by camel_hostname_utils_requires_ascii().
477 *
478 * Returns: %TRUE, when converted at least one address
479 *
480 * Since: 3.42.1
481 **/
482 gboolean
camel_internet_address_sanitize_ascii_domain(CamelInternetAddress * addr)483 camel_internet_address_sanitize_ascii_domain (CamelInternetAddress *addr)
484 {
485 struct _address *a;
486 gboolean did_convert = FALSE;
487 gint ii, len;
488
489 g_return_val_if_fail (CAMEL_IS_INTERNET_ADDRESS (addr), FALSE);
490
491 len = addr->priv->addresses->len;
492 for (ii = 0; ii < len; ii++) {
493 gint at_pos = -1;
494 a = g_ptr_array_index (addr->priv->addresses, ii);
495 if (a->address && !domain_contains_only_ascii (a->address, &at_pos) &&
496 at_pos >= 0 && at_pos + 1 < strlen (a->address) &&
497 camel_hostname_utils_requires_ascii (a->address + at_pos + 1)) {
498 gchar *address, *domain;
499
500 did_convert = TRUE;
501
502 domain = camel_host_idna_to_ascii (a->address + at_pos + 1);
503 if (at_pos >= 0) {
504 address = g_strdup_printf ("%.*s@%s", at_pos, a->address, domain);
505 } else {
506 address = domain;
507 domain = NULL;
508 }
509
510 g_free (domain);
511 g_free (a->address);
512 a->address = address;
513 }
514 }
515
516 return did_convert;
517 }
518
519 /**
520 * camel_internet_address_find_address:
521 * @addr: a #CamelInternetAddress object
522 * @address: address to lookup
523 * @namep: (out) (optional) (nullable) (transfer none): holder for the matching name, or %NULL, if not required.
524 *
525 * Find an address by address.
526 *
527 * Returns: the index of the address, or -1 if not found
528 **/
529 gint
camel_internet_address_find_address(CamelInternetAddress * addr,const gchar * address,const gchar ** namep)530 camel_internet_address_find_address (CamelInternetAddress *addr,
531 const gchar *address,
532 const gchar **namep)
533 {
534 struct _address *a;
535 gint i, len;
536
537 g_return_val_if_fail (CAMEL_IS_INTERNET_ADDRESS (addr), -1);
538
539 len = addr->priv->addresses->len;
540 for (i = 0; i < len; i++) {
541 a = g_ptr_array_index (addr->priv->addresses, i);
542 if (a->address && address && !g_ascii_strcasecmp (a->address, address)) {
543 if (namep)
544 *namep = a->name;
545 return i;
546 }
547 }
548 return -1;
549 }
550
551 static void
cia_encode_addrspec(GString * out,const gchar * addr)552 cia_encode_addrspec (GString *out,
553 const gchar *addr)
554 {
555 const gchar *at, *p;
556
557 at = strchr (addr, '@');
558 if (at == NULL)
559 goto append;
560
561 p = addr;
562 while (p < at) {
563 gchar c = *p++;
564
565 /* strictly by rfc, we should split local parts on dots.
566 * however i think 2822 changes this, and not many clients grok it, so
567 * just quote the whole local part if need be */
568 if (!(camel_mime_is_atom (c) || c == '.')) {
569 g_string_append_c (out, '"');
570
571 p = addr;
572 while (p < at) {
573 c = *p++;
574 if (c == '"' || c == '\\')
575 g_string_append_c (out, '\\');
576 g_string_append_c (out, c);
577 }
578 g_string_append_c (out, '"');
579 g_string_append (out, p);
580
581 return;
582 }
583 }
584
585 append:
586 g_string_append (out, addr);
587 }
588
589 /**
590 * camel_internet_address_encode_address:
591 * @len: the length of the line the address is being appended to
592 * @name: the unencoded real name associated with the address
593 * @addr: the routing address
594 *
595 * Encode a single address ready for internet usage. Header folding
596 * as per rfc822 is also performed, based on the length *@len. If @len
597 * is %NULL, then no folding will occur.
598 *
599 * Note: The value at *@in will be updated based on any linewrapping done
600 *
601 * Returns: the encoded address
602 **/
603 gchar *
camel_internet_address_encode_address(gint * inlen,const gchar * real,const gchar * addr)604 camel_internet_address_encode_address (gint *inlen,
605 const gchar *real,
606 const gchar *addr)
607 {
608 gchar *name;
609 gint len = 0;
610 GString *out;
611
612 g_return_val_if_fail (addr, NULL);
613
614 name = camel_header_encode_phrase ((const guchar *) real);
615 out = g_string_new ("");
616
617 if (inlen != NULL)
618 len = *inlen;
619
620 if (name && name[0]) {
621 if (inlen != NULL && (strlen (name) + len) > CAMEL_FOLD_SIZE) {
622 gchar *folded = camel_header_address_fold (name, len);
623 gchar *last;
624 g_string_append (out, folded);
625 g_free (folded);
626 last = strrchr (out->str, '\n');
627 if (last)
628 len = last - (out->str + out->len);
629 else
630 len = out->len;
631 } else {
632 g_string_append (out, name);
633 len += strlen (name);
634 }
635 }
636
637 /* NOTE: Strictly speaking, we could and should split the
638 * internal address up if we need to, on atom or specials
639 * boundaries - however, to aid interoperability with mailers
640 * that will probably not handle this case, we will just move
641 * the whole address to its own line. */
642 if (inlen != NULL && (strlen (addr) + len) > CAMEL_FOLD_SIZE) {
643 g_string_append (out, "\n\t");
644 len = 1;
645 }
646
647 len -= out->len;
648
649 if (name && name[0])
650 g_string_append (out, " <");
651 cia_encode_addrspec (out, addr);
652 if (name && name[0])
653 g_string_append_c (out, '>');
654
655 len += out->len;
656
657 if (inlen != NULL)
658 *inlen = len;
659
660 g_free (name);
661
662 return g_string_free (out, FALSE);
663 }
664
665 /**
666 * camel_internet_address_format_address:
667 * @name: a name, quotes may be stripped from it
668 * @addr: an rfc822 routing address
669 *
670 * Function to format a single address, suitable for display.
671 *
672 * Returns: a nicely formatted string containing the rfc822 address
673 **/
674 gchar *
camel_internet_address_format_address(const gchar * name,const gchar * addr)675 camel_internet_address_format_address (const gchar *name,
676 const gchar *addr)
677 {
678 gchar *ret = NULL;
679
680 g_return_val_if_fail (addr, NULL);
681
682 if (name && name[0]) {
683 const gchar *p = name;
684 gchar *o, c;
685
686 while ((c = *p++)) {
687 if (c == ',' || c == ';' || c == '\"' || c == '<' || c == '>') {
688 o = ret = g_malloc (strlen (name) + 3 + strlen (addr) + 3 + 1);
689 p = name;
690 *o++ = '\"';
691 while ((c = *p++))
692 if (c != '\"')
693 *o++ = c;
694 *o++ = '\"';
695 sprintf (o, " <%s>", addr);
696 d (printf ("encoded '%s' => '%s'\n", name, ret));
697 return ret;
698 }
699 }
700 ret = g_strdup_printf ("%s <%s>", name, addr);
701 } else
702 ret = g_strdup (addr);
703
704 return ret;
705 }
706