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