1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
2 /* GMime
3 * Copyright (C) 2000-2020 Jeffrey Stedfast
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public License
7 * as published by the Free Software Foundation; either version 2.1
8 * of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free
17 * Software Foundation, 51 Franklin Street, Fifth Floor, Boston, MA
18 * 02110-1301, USA.
19 */
20
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <ctype.h>
30 #include <errno.h>
31
32 #ifdef LIBIDN
33 #include <idn2.h>
34 #undef G_GNUC_UNUSED
35 #undef G_GNUC_DEPRECATED
36 #endif
37
38 #include "internet-address.h"
39 #include "gmime-table-private.h"
40 #include "gmime-parse-utils.h"
41 #include "gmime-iconv-utils.h"
42 #include "gmime-internal.h"
43 #include "gmime-events.h"
44 #include "gmime-utils.h"
45
46
47 #ifdef ENABLE_WARNINGS
48 #define w(x) x
49 #else
50 #define w(x)
51 #endif /* ENABLE_WARNINGS */
52
53 #define d(x)
54
55
56 /**
57 * SECTION: internet-address
58 * @title: InternetAddress
59 * @short_description: Internet addresses
60 * @see_also: #InternetAddressGroup, #InternetAddressMailbox
61 *
62 * An #InternetAddress is the base class for #InternetAddressGroup and
63 * #InternetAddressMailbox.
64 **/
65
66 /**
67 * SECTION: internet-address-group
68 * @title: InternetAddressGroup
69 * @short_description: rfc822 'group' address
70 * @see_also: #InternetAddress
71 *
72 * An #InternetAddressGroup represents an rfc822 'group' address.
73 **/
74
75 /**
76 * SECTION: internet-address-mailbox
77 * @title: InternetAddressMailbox
78 * @short_description: rfc822 'mailbox' address
79 * @see_also: #InternetAddress
80 *
81 * An #InternetAddressMailbox represents what is a typical "email
82 * address".
83 **/
84
85 /**
86 * SECTION: internet-address-list
87 * @title: InternetAddressList
88 * @short_description: A list of internet addresses
89 * @see_also: #InternetAddress
90 *
91 * An #InternetAddressList is a collection of #InternetAddress
92 * objects.
93 **/
94
95
96 enum {
97 INTERNET_ADDRESS_ENCODE = 1 << 0,
98 INTERNET_ADDRESS_FOLD = 1 << 1,
99 };
100
101 static gboolean addrspec_parse (const char **in, const char *sentinels, char **addrspec, int *at);
102
103 static void internet_address_class_init (InternetAddressClass *klass);
104 static void internet_address_init (InternetAddress *ia, InternetAddressClass *klass);
105 static void internet_address_finalize (GObject *object);
106
107
108 static GObjectClass *parent_class = NULL;
109
110
111 GType
internet_address_get_type(void)112 internet_address_get_type (void)
113 {
114 static GType type = 0;
115
116 if (!type) {
117 static const GTypeInfo info = {
118 sizeof (InternetAddressClass),
119 NULL, /* base_class_init */
120 NULL, /* base_class_finalize */
121 (GClassInitFunc) internet_address_class_init,
122 NULL, /* class_finalize */
123 NULL, /* class_data */
124 sizeof (InternetAddress),
125 0, /* n_preallocs */
126 (GInstanceInitFunc) internet_address_init,
127 };
128
129 type = g_type_register_static (G_TYPE_OBJECT, "InternetAddress",
130 &info, G_TYPE_FLAG_ABSTRACT);
131 }
132
133 return type;
134 }
135
136
137 static void
internet_address_class_init(InternetAddressClass * klass)138 internet_address_class_init (InternetAddressClass *klass)
139 {
140 GObjectClass *object_class = G_OBJECT_CLASS (klass);
141
142 parent_class = g_type_class_ref (G_TYPE_OBJECT);
143
144 object_class->finalize = internet_address_finalize;
145
146 klass->to_string = NULL;
147 }
148
149 static void
internet_address_init(InternetAddress * ia,InternetAddressClass * klass)150 internet_address_init (InternetAddress *ia, InternetAddressClass *klass)
151 {
152 ia->changed = g_mime_event_new ((GObject *) ia);
153 ia->charset = NULL;
154 ia->name = NULL;
155 }
156
157 static void
internet_address_finalize(GObject * object)158 internet_address_finalize (GObject *object)
159 {
160 InternetAddress *ia = (InternetAddress *) object;
161
162 g_mime_event_free (ia->changed);
163 g_free (ia->charset);
164 g_free (ia->name);
165
166 G_OBJECT_CLASS (parent_class)->finalize (object);
167 }
168
169
170 static void
_internet_address_set_name(InternetAddress * ia,const char * name)171 _internet_address_set_name (InternetAddress *ia, const char *name)
172 {
173 char *buf;
174
175 buf = g_strdup (name);
176 g_free (ia->name);
177 ia->name = buf;
178 }
179
180
181 /**
182 * internet_address_set_name:
183 * @ia: a #InternetAddress
184 * @name: the display name for the address group or mailbox
185 *
186 * Set the display name of the #InternetAddress.
187 *
188 * Note: The @name string should be in UTF-8.
189 **/
190 void
internet_address_set_name(InternetAddress * ia,const char * name)191 internet_address_set_name (InternetAddress *ia, const char *name)
192 {
193 g_return_if_fail (IS_INTERNET_ADDRESS (ia));
194
195 _internet_address_set_name (ia, name);
196
197 g_mime_event_emit (ia->changed, NULL);
198 }
199
200
201 /**
202 * internet_address_get_name:
203 * @ia: a #InternetAddress
204 *
205 * Gets the display name of the #InternetAddress.
206 *
207 * Returns: (nullable): the name of the mailbox or group in a form suitable
208 * for display if available or %NULL otherwise. If the name is available,
209 * the returned string will be in UTF-8.
210 **/
211 const char *
internet_address_get_name(InternetAddress * ia)212 internet_address_get_name (InternetAddress *ia)
213 {
214 g_return_val_if_fail (IS_INTERNET_ADDRESS (ia), NULL);
215
216 return ia->name;
217 }
218
219
220 /**
221 * internet_address_set_charset:
222 * @ia: a #InternetAddress
223 * @charset: (nullable): the charset to use when encoding the name or %NULL to use the defaults
224 *
225 * Set the charset to use for encoding the name of the mailbox or group.
226 **/
227 void
internet_address_set_charset(InternetAddress * ia,const char * charset)228 internet_address_set_charset (InternetAddress *ia, const char *charset)
229 {
230 char *buf;
231
232 g_return_if_fail (IS_INTERNET_ADDRESS (ia));
233
234 buf = charset ? g_strdup (charset) : NULL;
235 g_free (ia->charset);
236 ia->charset = buf;
237
238 g_mime_event_emit (ia->changed, NULL);
239 }
240
241
242 /**
243 * internet_address_get_charset:
244 * @ia: a #InternetAddress
245 *
246 * Gets the charset to be used when encoding the name of the mailbox or group.
247 *
248 * Returns: (nullable): the charset to be used when encoding the name of the
249 * mailbox or group if available or %NULL otherwise.
250 **/
251 const char *
internet_address_get_charset(InternetAddress * ia)252 internet_address_get_charset (InternetAddress *ia)
253 {
254 g_return_val_if_fail (IS_INTERNET_ADDRESS (ia), NULL);
255
256 return ia->charset;
257 }
258
259
260 /**
261 * internet_address_to_string:
262 * @ia: Internet Address object
263 * @options: (nullable): a #GMimeFormatOptions or %NULL
264 * @encode: %TRUE if the address should be rfc2047 encoded
265 *
266 * Allocates a string containing the contents of the #InternetAddress
267 * object.
268 *
269 * Returns: the #InternetAddress object as an allocated string in
270 * rfc822 format.
271 **/
272 char *
internet_address_to_string(InternetAddress * ia,GMimeFormatOptions * options,gboolean encode)273 internet_address_to_string (InternetAddress *ia, GMimeFormatOptions *options, gboolean encode)
274 {
275 guint32 flags = encode ? INTERNET_ADDRESS_ENCODE : 0;
276 size_t linelen = 0;
277 GString *string;
278 char *str;
279
280 string = g_string_new ("");
281 INTERNET_ADDRESS_GET_CLASS (ia)->to_string (ia, options, flags, &linelen, string);
282 str = string->str;
283
284 g_string_free (string, FALSE);
285
286 return str;
287 }
288
289
290 static void internet_address_mailbox_class_init (InternetAddressMailboxClass *klass);
291 static void internet_address_mailbox_init (InternetAddressMailbox *mailbox, InternetAddressMailboxClass *klass);
292 static void internet_address_mailbox_finalize (GObject *object);
293
294 static void mailbox_to_string (InternetAddress *ia, GMimeFormatOptions *options, guint32 flags,
295 size_t *linelen, GString *out);
296
297
298 static GObjectClass *mailbox_parent_class = NULL;
299
300
301 GType
internet_address_mailbox_get_type(void)302 internet_address_mailbox_get_type (void)
303 {
304 static GType type = 0;
305
306 if (!type) {
307 static const GTypeInfo info = {
308 sizeof (InternetAddressMailboxClass),
309 NULL, /* base_class_init */
310 NULL, /* base_class_finalize */
311 (GClassInitFunc) internet_address_mailbox_class_init,
312 NULL, /* class_finalize */
313 NULL, /* class_data */
314 sizeof (InternetAddressMailbox),
315 0, /* n_preallocs */
316 (GInstanceInitFunc) internet_address_mailbox_init,
317 };
318
319 type = g_type_register_static (INTERNET_ADDRESS_TYPE, "InternetAddressMailbox", &info, 0);
320 }
321
322 return type;
323 }
324
325
326 static void
internet_address_mailbox_class_init(InternetAddressMailboxClass * klass)327 internet_address_mailbox_class_init (InternetAddressMailboxClass *klass)
328 {
329 InternetAddressClass *address_class = INTERNET_ADDRESS_CLASS (klass);
330 GObjectClass *object_class = G_OBJECT_CLASS (klass);
331
332 mailbox_parent_class = g_type_class_ref (INTERNET_ADDRESS_TYPE);
333
334 object_class->finalize = internet_address_mailbox_finalize;
335
336 address_class->to_string = mailbox_to_string;
337 }
338
339 static void
internet_address_mailbox_init(InternetAddressMailbox * mailbox,InternetAddressMailboxClass * klass)340 internet_address_mailbox_init (InternetAddressMailbox *mailbox, InternetAddressMailboxClass *klass)
341 {
342 mailbox->idn_addr = NULL;
343 mailbox->addr = NULL;
344 mailbox->at = -1;
345 }
346
347 static void
internet_address_mailbox_finalize(GObject * object)348 internet_address_mailbox_finalize (GObject *object)
349 {
350 InternetAddressMailbox *mailbox = (InternetAddressMailbox *) object;
351
352 g_free (mailbox->idn_addr);
353 g_free (mailbox->addr);
354
355 G_OBJECT_CLASS (mailbox_parent_class)->finalize (object);
356 }
357
358 static InternetAddress *
_internet_address_mailbox_new(const char * name,const char * addr,int at)359 _internet_address_mailbox_new (const char *name, const char *addr, int at)
360 {
361 InternetAddressMailbox *mailbox;
362
363 mailbox = g_object_new (INTERNET_ADDRESS_TYPE_MAILBOX, NULL);
364 mailbox->addr = g_strdup (addr);
365 mailbox->at = at;
366
367 _internet_address_set_name ((InternetAddress *) mailbox, name);
368
369 return (InternetAddress *) mailbox;
370 }
371
372
373 /**
374 * internet_address_mailbox_new:
375 * @name: person's name
376 * @addr: person's address
377 *
378 * Creates a new #InternetAddress object with the specified @name and
379 * @addr.
380 *
381 * Returns: a new #InternetAddressMailbox object.
382 *
383 * Note: The @name string should be in UTF-8.
384 **/
385 InternetAddress *
internet_address_mailbox_new(const char * name,const char * addr)386 internet_address_mailbox_new (const char *name, const char *addr)
387 {
388 InternetAddressMailbox *mailbox;
389 const char *inptr = addr;
390
391 g_return_val_if_fail (addr != NULL, NULL);
392
393 mailbox = g_object_new (INTERNET_ADDRESS_TYPE_MAILBOX, NULL);
394
395 if (!addrspec_parse (&inptr, "", &mailbox->addr, &mailbox->at))
396 mailbox->addr = g_strdup (addr);
397
398 _internet_address_set_name ((InternetAddress *) mailbox, name);
399
400 return (InternetAddress *) mailbox;
401 }
402
403
404 /**
405 * internet_address_mailbox_set_addr:
406 * @mailbox: a #InternetAddressMailbox
407 * @addr: contact's email address
408 *
409 * Set the mailbox address.
410 **/
411 void
internet_address_mailbox_set_addr(InternetAddressMailbox * mailbox,const char * addr)412 internet_address_mailbox_set_addr (InternetAddressMailbox *mailbox, const char *addr)
413 {
414 const char *inptr = addr;
415
416 g_return_if_fail (INTERNET_ADDRESS_IS_MAILBOX (mailbox));
417
418 if (mailbox->addr == addr)
419 return;
420
421 g_free (mailbox->idn_addr);
422 mailbox->idn_addr = NULL;
423
424 g_free (mailbox->addr);
425
426 if (!addrspec_parse (&inptr, "", &mailbox->addr, &mailbox->at))
427 mailbox->addr = g_strdup (addr);
428
429 g_mime_event_emit (((InternetAddress *) mailbox)->changed, NULL);
430 }
431
432
433 /**
434 * internet_address_mailbox_get_addr:
435 * @mailbox: a #InternetAddressMailbox
436 *
437 * Gets the addr-spec of the internet address mailbox.
438 *
439 * Returns: the addr-spec string.
440 **/
441 const char *
internet_address_mailbox_get_addr(InternetAddressMailbox * mailbox)442 internet_address_mailbox_get_addr (InternetAddressMailbox *mailbox)
443 {
444 g_return_val_if_fail (INTERNET_ADDRESS_IS_MAILBOX (mailbox), NULL);
445
446 return mailbox->addr;
447 }
448
449
450 /**
451 * internet_address_mailbox_get_idn_addr:
452 * @mailbox: a #InternetAddressMailbox
453 *
454 * Gets the IDN ascii-encoded addr-spec.
455 *
456 * Returns: the encoded addr-spec string.
457 **/
458 const char *
internet_address_mailbox_get_idn_addr(InternetAddressMailbox * mailbox)459 internet_address_mailbox_get_idn_addr (InternetAddressMailbox *mailbox)
460 {
461 GString *encoded;
462 char *ascii;
463
464 g_return_val_if_fail (INTERNET_ADDRESS_IS_MAILBOX (mailbox), NULL);
465
466 #ifdef LIBIDN
467 if (!mailbox->idn_addr && mailbox->at > 0) {
468 const char *domain = mailbox->addr + mailbox->at + 1;
469
470 encoded = g_string_new ("");
471 g_string_append_len (encoded, mailbox->addr, mailbox->at + 1);
472 if (idn2_to_ascii_8z (domain, &ascii, 0) == IDN2_OK) {
473 if (!g_ascii_strcasecmp (domain, ascii))
474 g_string_append (encoded, domain);
475 else
476 g_string_append (encoded, ascii);
477 idn2_free (ascii);
478 } else {
479 g_string_append (encoded, domain);
480 }
481
482 mailbox->idn_addr = g_string_free (encoded, FALSE);
483 }
484
485 return mailbox->idn_addr ? mailbox->idn_addr : mailbox->addr;
486 #else
487 return mailbox->addr;
488 #endif
489 }
490
491
492 static void internet_address_group_class_init (InternetAddressGroupClass *klass);
493 static void internet_address_group_init (InternetAddressGroup *group, InternetAddressGroupClass *klass);
494 static void internet_address_group_finalize (GObject *object);
495
496 static void group_to_string (InternetAddress *ia, GMimeFormatOptions *options, guint32 flags,
497 size_t *linelen, GString *out);
498
499
500 static GObjectClass *group_parent_class = NULL;
501
502
503 GType
internet_address_group_get_type(void)504 internet_address_group_get_type (void)
505 {
506 static GType type = 0;
507
508 if (!type) {
509 static const GTypeInfo info = {
510 sizeof (InternetAddressGroupClass),
511 NULL, /* base_class_init */
512 NULL, /* base_class_finalize */
513 (GClassInitFunc) internet_address_group_class_init,
514 NULL, /* class_finalize */
515 NULL, /* class_data */
516 sizeof (InternetAddressGroup),
517 0, /* n_preallocs */
518 (GInstanceInitFunc) internet_address_group_init,
519 };
520
521 type = g_type_register_static (INTERNET_ADDRESS_TYPE, "InternetAddressGroup", &info, 0);
522 }
523
524 return type;
525 }
526
527
528 static void
internet_address_group_class_init(InternetAddressGroupClass * klass)529 internet_address_group_class_init (InternetAddressGroupClass *klass)
530 {
531 InternetAddressClass *address_class = INTERNET_ADDRESS_CLASS (klass);
532 GObjectClass *object_class = G_OBJECT_CLASS (klass);
533
534 group_parent_class = g_type_class_ref (INTERNET_ADDRESS_TYPE);
535
536 object_class->finalize = internet_address_group_finalize;
537
538 address_class->to_string = group_to_string;
539 }
540
541 static void
members_changed(InternetAddressList * members,gpointer args,InternetAddress * group)542 members_changed (InternetAddressList *members, gpointer args, InternetAddress *group)
543 {
544 g_mime_event_emit (((InternetAddress *) group)->changed, NULL);
545 }
546
547 static void
internet_address_group_init(InternetAddressGroup * group,InternetAddressGroupClass * klass)548 internet_address_group_init (InternetAddressGroup *group, InternetAddressGroupClass *klass)
549 {
550 group->members = internet_address_list_new ();
551
552 g_mime_event_add (group->members->changed, (GMimeEventCallback) members_changed, group);
553 }
554
555 static void
internet_address_group_finalize(GObject * object)556 internet_address_group_finalize (GObject *object)
557 {
558 InternetAddressGroup *group = (InternetAddressGroup *) object;
559
560 g_mime_event_remove (group->members->changed, (GMimeEventCallback) members_changed, group);
561
562 g_object_unref (group->members);
563
564 G_OBJECT_CLASS (group_parent_class)->finalize (object);
565 }
566
567
568 /**
569 * internet_address_group_new:
570 * @name: group name
571 *
572 * Creates a new #InternetAddressGroup object with the specified
573 * @name.
574 *
575 * Returns: a new #InternetAddressGroup object.
576 *
577 * Note: The @name string should be in UTF-8.
578 **/
579 InternetAddress *
internet_address_group_new(const char * name)580 internet_address_group_new (const char *name)
581 {
582 InternetAddress *group;
583
584 group = g_object_new (INTERNET_ADDRESS_TYPE_GROUP, NULL);
585 _internet_address_set_name (group, name);
586
587 return group;
588 }
589
590
591 /**
592 * internet_address_group_set_members:
593 * @group: a #InternetAddressGroup
594 * @members: a #InternetAddressList
595 *
596 * Set the members of the internet address group.
597 **/
598 void
internet_address_group_set_members(InternetAddressGroup * group,InternetAddressList * members)599 internet_address_group_set_members (InternetAddressGroup *group, InternetAddressList *members)
600 {
601 g_return_if_fail (INTERNET_ADDRESS_IS_GROUP (group));
602 g_return_if_fail (IS_INTERNET_ADDRESS_LIST (members));
603
604 if (group->members == members)
605 return;
606
607 if (group->members) {
608 g_mime_event_remove (group->members->changed, (GMimeEventCallback) members_changed, group);
609 g_object_unref (group->members);
610 }
611
612 if (members) {
613 g_mime_event_add (members->changed, (GMimeEventCallback) members_changed, group);
614 g_object_ref (members);
615 }
616
617 group->members = members;
618
619 g_mime_event_emit (((InternetAddress *) group)->changed, NULL);
620 }
621
622
623 /**
624 * internet_address_group_get_members:
625 * @group: a #InternetAddressGroup
626 *
627 * Gets the #InternetAddressList containing the group members of an
628 * rfc822 group address.
629 *
630 * Returns: (transfer none): a #InternetAddressList containing the
631 * members of @group.
632 **/
633 InternetAddressList *
internet_address_group_get_members(InternetAddressGroup * group)634 internet_address_group_get_members (InternetAddressGroup *group)
635 {
636 g_return_val_if_fail (INTERNET_ADDRESS_IS_GROUP (group), NULL);
637
638 return group->members;
639 }
640
641
642 #define _internet_address_group_add_member(group,member) _internet_address_list_add (group->members, member)
643
644 /**
645 * internet_address_group_add_member:
646 * @group: a #InternetAddressGroup
647 * @member: a #InternetAddress
648 *
649 * Add a contact to the internet address group.
650 *
651 * Returns: the index of the newly added member.
652 **/
653 int
internet_address_group_add_member(InternetAddressGroup * group,InternetAddress * member)654 internet_address_group_add_member (InternetAddressGroup *group, InternetAddress *member)
655 {
656 g_return_val_if_fail (INTERNET_ADDRESS_IS_GROUP (group), -1);
657 g_return_val_if_fail (IS_INTERNET_ADDRESS (member), -1);
658
659 return internet_address_list_add (group->members, member);
660 }
661
662
663 static void internet_address_list_class_init (InternetAddressListClass *klass);
664 static void internet_address_list_init (InternetAddressList *list, InternetAddressListClass *klass);
665 static void internet_address_list_finalize (GObject *object);
666
667
668 static GObjectClass *list_parent_class = NULL;
669
670
671 GType
internet_address_list_get_type(void)672 internet_address_list_get_type (void)
673 {
674 static GType type = 0;
675
676 if (!type) {
677 static const GTypeInfo info = {
678 sizeof (InternetAddressListClass),
679 NULL, /* base_class_init */
680 NULL, /* base_class_finalize */
681 (GClassInitFunc) internet_address_list_class_init,
682 NULL, /* class_finalize */
683 NULL, /* class_data */
684 sizeof (InternetAddressList),
685 0, /* n_preallocs */
686 (GInstanceInitFunc) internet_address_list_init,
687 };
688
689 type = g_type_register_static (G_TYPE_OBJECT, "InternetAddressList", &info, 0);
690 }
691
692 return type;
693 }
694
695
696 static void
internet_address_list_class_init(InternetAddressListClass * klass)697 internet_address_list_class_init (InternetAddressListClass *klass)
698 {
699 GObjectClass *object_class = G_OBJECT_CLASS (klass);
700
701 list_parent_class = g_type_class_ref (G_TYPE_OBJECT);
702
703 object_class->finalize = internet_address_list_finalize;
704 }
705
706 static void
internet_address_list_init(InternetAddressList * list,InternetAddressListClass * klass)707 internet_address_list_init (InternetAddressList *list, InternetAddressListClass *klass)
708 {
709 list->changed = g_mime_event_new ((GObject *) list);
710 list->array = g_ptr_array_new ();
711 }
712
713 static void
address_changed(InternetAddress * ia,gpointer args,InternetAddressList * list)714 address_changed (InternetAddress *ia, gpointer args, InternetAddressList *list)
715 {
716 g_mime_event_emit (list->changed, NULL);
717 }
718
719 static void
internet_address_list_finalize(GObject * object)720 internet_address_list_finalize (GObject *object)
721 {
722 InternetAddressList *list = (InternetAddressList *) object;
723 InternetAddress *ia;
724 guint i;
725
726 for (i = 0; i < list->array->len; i++) {
727 ia = (InternetAddress *) list->array->pdata[i];
728 g_mime_event_remove (ia->changed, (GMimeEventCallback) address_changed, list);
729 g_object_unref (ia);
730 }
731
732 g_mime_event_free (list->changed);
733
734 g_ptr_array_free (list->array, TRUE);
735
736 G_OBJECT_CLASS (list_parent_class)->finalize (object);
737 }
738
739
740 /**
741 * internet_address_list_new:
742 *
743 * Creates a new #InternetAddressList.
744 *
745 * Returns: a new #InternetAddressList.
746 **/
747 InternetAddressList *
internet_address_list_new(void)748 internet_address_list_new (void)
749 {
750 return g_object_new (INTERNET_ADDRESS_LIST_TYPE, NULL);
751 }
752
753
754 /**
755 * internet_address_list_length:
756 * @list: a #InternetAddressList
757 *
758 * Gets the length of the list.
759 *
760 * Returns: the number of #InternetAddress objects in the list.
761 **/
762 int
internet_address_list_length(InternetAddressList * list)763 internet_address_list_length (InternetAddressList *list)
764 {
765 g_return_val_if_fail (IS_INTERNET_ADDRESS_LIST (list), -1);
766
767 return list->array->len;
768 }
769
770
771 /**
772 * internet_address_list_clear:
773 * @list: a #InternetAddressList
774 *
775 * Clears the list of addresses.
776 **/
777 void
internet_address_list_clear(InternetAddressList * list)778 internet_address_list_clear (InternetAddressList *list)
779 {
780 InternetAddress *ia;
781 guint i;
782
783 g_return_if_fail (IS_INTERNET_ADDRESS_LIST (list));
784
785 for (i = 0; i < list->array->len; i++) {
786 ia = (InternetAddress *) list->array->pdata[i];
787 g_mime_event_remove (ia->changed, (GMimeEventCallback) address_changed, list);
788 g_object_unref (ia);
789 }
790
791 g_ptr_array_set_size (list->array, 0);
792
793 g_mime_event_emit (list->changed, NULL);
794 }
795
796
797 static int
_internet_address_list_add(InternetAddressList * list,InternetAddress * ia)798 _internet_address_list_add (InternetAddressList *list, InternetAddress *ia)
799 {
800 int index;
801
802 g_mime_event_add (ia->changed, (GMimeEventCallback) address_changed, list);
803
804 index = list->array->len;
805 g_ptr_array_add (list->array, ia);
806
807 return index;
808 }
809
810
811 /**
812 * internet_address_list_add:
813 * @list: a #InternetAddressList
814 * @ia: a #InternetAddress
815 *
816 * Adds an #InternetAddress to the #InternetAddressList.
817 *
818 * Returns: the index of the added #InternetAddress.
819 **/
820 int
internet_address_list_add(InternetAddressList * list,InternetAddress * ia)821 internet_address_list_add (InternetAddressList *list, InternetAddress *ia)
822 {
823 int index;
824
825 g_return_val_if_fail (IS_INTERNET_ADDRESS_LIST (list), -1);
826 g_return_val_if_fail (IS_INTERNET_ADDRESS (ia), -1);
827
828 index = _internet_address_list_add (list, ia);
829 g_object_ref (ia);
830
831 g_mime_event_emit (list->changed, NULL);
832
833 return index;
834 }
835
836
837 /**
838 * internet_address_list_prepend:
839 * @list: a #InternetAddressList
840 * @prepend: a #InternetAddressList
841 *
842 * Inserts all of the addresses in @prepend to the beginning of @list.
843 **/
844 void
internet_address_list_prepend(InternetAddressList * list,InternetAddressList * prepend)845 internet_address_list_prepend (InternetAddressList *list, InternetAddressList *prepend)
846 {
847 InternetAddress *ia;
848 char *dest, *src;
849 guint len, i;
850
851 g_return_if_fail (IS_INTERNET_ADDRESS_LIST (prepend));
852 g_return_if_fail (IS_INTERNET_ADDRESS_LIST (list));
853
854 if (prepend->array->len == 0)
855 return;
856
857 len = prepend->array->len;
858 g_ptr_array_set_size (list->array, list->array->len + len);
859
860 src = ((char *) list->array->pdata);
861 dest = src + (sizeof (void *) * len);
862
863 memmove (dest, src, (sizeof (void *) * list->array->len));
864
865 for (i = 0; i < prepend->array->len; i++) {
866 ia = (InternetAddress *) prepend->array->pdata[i];
867 g_mime_event_add (ia->changed, (GMimeEventCallback) address_changed, list);
868 list->array->pdata[i] = ia;
869 g_object_ref (ia);
870 }
871
872 g_mime_event_emit (list->changed, NULL);
873 }
874
875
876 /**
877 * internet_address_list_append:
878 * @list: a #InternetAddressList
879 * @append: a #InternetAddressList
880 *
881 * Adds all of the addresses in @append to @list.
882 **/
883 void
internet_address_list_append(InternetAddressList * list,InternetAddressList * append)884 internet_address_list_append (InternetAddressList *list, InternetAddressList *append)
885 {
886 InternetAddress *ia;
887 guint len, i;
888
889 g_return_if_fail (IS_INTERNET_ADDRESS_LIST (append));
890 g_return_if_fail (IS_INTERNET_ADDRESS_LIST (list));
891
892 len = list->array->len;
893 g_ptr_array_set_size (list->array, len + append->array->len);
894
895 for (i = 0; i < append->array->len; i++) {
896 ia = (InternetAddress *) append->array->pdata[i];
897 g_mime_event_add (ia->changed, (GMimeEventCallback) address_changed, list);
898 list->array->pdata[len + i] = ia;
899 g_object_ref (ia);
900 }
901
902 g_mime_event_emit (list->changed, NULL);
903 }
904
905
906 /**
907 * internet_address_list_insert:
908 * @list: a #InternetAddressList
909 * @index: index to insert at
910 * @ia: a #InternetAddress
911 *
912 * Inserts an #InternetAddress into the #InternetAddressList at the
913 * specified index.
914 **/
915 void
internet_address_list_insert(InternetAddressList * list,int index,InternetAddress * ia)916 internet_address_list_insert (InternetAddressList *list, int index, InternetAddress *ia)
917 {
918 char *dest, *src;
919 size_t n;
920
921 g_return_if_fail (IS_INTERNET_ADDRESS_LIST (list));
922 g_return_if_fail (IS_INTERNET_ADDRESS (ia));
923 g_return_if_fail (index >= 0);
924
925 g_mime_event_add (ia->changed, (GMimeEventCallback) address_changed, list);
926 g_object_ref (ia);
927
928 if ((guint) index < list->array->len) {
929 g_ptr_array_set_size (list->array, list->array->len + 1);
930
931 dest = ((char *) list->array->pdata) + (sizeof (void *) * (index + 1));
932 src = ((char *) list->array->pdata) + (sizeof (void *) * index);
933 n = list->array->len - index - 1;
934
935 memmove (dest, src, (sizeof (void *) * n));
936 list->array->pdata[index] = ia;
937 } else {
938 /* the easy case */
939 g_ptr_array_add (list->array, ia);
940 }
941
942 g_mime_event_emit (list->changed, NULL);
943 }
944
945
946 /**
947 * internet_address_list_remove:
948 * @list: a #InternetAddressList
949 * @ia: a #InternetAddress
950 *
951 * Removes an #InternetAddress from the #InternetAddressList.
952 *
953 * Returns: %TRUE if the specified #InternetAddress was removed or
954 * %FALSE otherwise.
955 **/
956 gboolean
internet_address_list_remove(InternetAddressList * list,InternetAddress * ia)957 internet_address_list_remove (InternetAddressList *list, InternetAddress *ia)
958 {
959 int index;
960
961 g_return_val_if_fail (IS_INTERNET_ADDRESS_LIST (list), FALSE);
962 g_return_val_if_fail (IS_INTERNET_ADDRESS (ia), FALSE);
963
964 if ((index = internet_address_list_index_of (list, ia)) == -1)
965 return FALSE;
966
967 internet_address_list_remove_at (list, index);
968
969 return TRUE;
970 }
971
972
973 /**
974 * internet_address_list_remove_at:
975 * @list: a #InternetAddressList
976 * @index: index to remove
977 *
978 * Removes an #InternetAddress from the #InternetAddressList at the
979 * specified index.
980 *
981 * Returns: %TRUE if an #InternetAddress was removed or %FALSE
982 * otherwise.
983 **/
984 gboolean
internet_address_list_remove_at(InternetAddressList * list,int index)985 internet_address_list_remove_at (InternetAddressList *list, int index)
986 {
987 InternetAddress *ia;
988
989 g_return_val_if_fail (IS_INTERNET_ADDRESS_LIST (list), FALSE);
990 g_return_val_if_fail (index >= 0, FALSE);
991
992 if ((guint) index >= list->array->len)
993 return FALSE;
994
995 ia = list->array->pdata[index];
996 g_mime_event_remove (ia->changed, (GMimeEventCallback) address_changed, list);
997 g_object_unref (ia);
998
999 g_ptr_array_remove_index (list->array, index);
1000
1001 g_mime_event_emit (list->changed, NULL);
1002
1003 return TRUE;
1004 }
1005
1006
1007 /**
1008 * internet_address_list_contains:
1009 * @list: a #InternetAddressList
1010 * @ia: a #InternetAddress
1011 *
1012 * Checks whether or not the specified #InternetAddress is contained
1013 * within the #InternetAddressList.
1014 *
1015 * Returns: %TRUE if the specified #InternetAddress is contained
1016 * within the specified #InternetAddressList or %FALSE otherwise.
1017 **/
1018 gboolean
internet_address_list_contains(InternetAddressList * list,InternetAddress * ia)1019 internet_address_list_contains (InternetAddressList *list, InternetAddress *ia)
1020 {
1021 return internet_address_list_index_of (list, ia) != -1;
1022 }
1023
1024
1025 /**
1026 * internet_address_list_index_of:
1027 * @list: a #InternetAddressList
1028 * @ia: a #InternetAddress
1029 *
1030 * Gets the index of the specified #InternetAddress inside the
1031 * #InternetAddressList.
1032 *
1033 * Returns: the index of the requested #InternetAddress within the
1034 * #InternetAddressList or %-1 if it is not contained within the
1035 * #InternetAddressList.
1036 **/
1037 int
internet_address_list_index_of(InternetAddressList * list,InternetAddress * ia)1038 internet_address_list_index_of (InternetAddressList *list, InternetAddress *ia)
1039 {
1040 guint i;
1041
1042 g_return_val_if_fail (IS_INTERNET_ADDRESS_LIST (list), -1);
1043 g_return_val_if_fail (IS_INTERNET_ADDRESS (ia), -1);
1044
1045 for (i = 0; i < list->array->len; i++) {
1046 if (list->array->pdata[i] == ia)
1047 return i;
1048 }
1049
1050 return -1;
1051 }
1052
1053
1054 /**
1055 * internet_address_list_get_address:
1056 * @list: a #InternetAddressList
1057 * @index: index of #InternetAddress to get
1058 *
1059 * Gets the #InternetAddress at the specified index.
1060 *
1061 * Returns: (transfer none): the #InternetAddress at the specified
1062 * index or %NULL if the index is out of range.
1063 **/
1064 InternetAddress *
internet_address_list_get_address(InternetAddressList * list,int index)1065 internet_address_list_get_address (InternetAddressList *list, int index)
1066 {
1067 g_return_val_if_fail (IS_INTERNET_ADDRESS_LIST (list), NULL);
1068 g_return_val_if_fail (index >= 0, NULL);
1069
1070 if ((guint) index >= list->array->len)
1071 return NULL;
1072
1073 return list->array->pdata[index];
1074 }
1075
1076
1077 /**
1078 * internet_address_list_set_address:
1079 * @list: a #InternetAddressList
1080 * @index: index of #InternetAddress to set
1081 * @ia: a #InternetAddress
1082 *
1083 * Sets the #InternetAddress at the specified index to @ia.
1084 **/
1085 void
internet_address_list_set_address(InternetAddressList * list,int index,InternetAddress * ia)1086 internet_address_list_set_address (InternetAddressList *list, int index, InternetAddress *ia)
1087 {
1088 InternetAddress *old;
1089
1090 g_return_if_fail (IS_INTERNET_ADDRESS_LIST (list));
1091 g_return_if_fail (IS_INTERNET_ADDRESS (ia));
1092 g_return_if_fail (index >= 0);
1093
1094 if ((guint) index > list->array->len)
1095 return;
1096
1097 if ((guint) index == list->array->len) {
1098 internet_address_list_add (list, ia);
1099 return;
1100 }
1101
1102 if ((old = list->array->pdata[index]) == ia)
1103 return;
1104
1105 g_mime_event_remove (old->changed, (GMimeEventCallback) address_changed, list);
1106 g_object_unref (old);
1107
1108 g_mime_event_add (ia->changed, (GMimeEventCallback) address_changed, list);
1109 list->array->pdata[index] = ia;
1110 g_object_ref (ia);
1111
1112 g_mime_event_emit (list->changed, NULL);
1113 }
1114
1115
1116 static char *
encoded_name(GMimeFormatOptions * options,const char * raw,gboolean rfc2047_encode,const char * charset)1117 encoded_name (GMimeFormatOptions *options, const char *raw, gboolean rfc2047_encode, const char *charset)
1118 {
1119 char *name;
1120
1121 g_return_val_if_fail (raw != NULL, NULL);
1122
1123 if (rfc2047_encode) {
1124 name = g_mime_utils_header_encode_phrase (options, raw, charset);
1125 } else {
1126 name = g_mime_utils_quote_string (raw);
1127 }
1128
1129 return name;
1130 }
1131
1132 static void
linewrap(GString * str,const char * newline)1133 linewrap (GString *str, const char *newline)
1134 {
1135 if (str->len > 0 && str->str[str->len - 1] == ' ') {
1136 str->str[str->len - 1] = newline[0];
1137
1138 if (newline[1])
1139 g_string_append_c (str, newline[1]);
1140 } else {
1141 g_string_append (str, newline);
1142 }
1143
1144 g_string_append_c (str, '\t');
1145 }
1146
1147 static void
append_folded_name(GString * str,size_t * linelen,const char * name,const char * newline)1148 append_folded_name (GString *str, size_t *linelen, const char *name, const char *newline)
1149 {
1150 const char *word, *lwsp;
1151 size_t len;
1152
1153 word = name;
1154
1155 while (*word) {
1156 lwsp = word;
1157
1158 if (*word == '"') {
1159 /* quoted string, don't break these up */
1160 lwsp++;
1161
1162 while (*lwsp && *lwsp != '"') {
1163 if (*lwsp == '\\')
1164 lwsp++;
1165
1166 if (*lwsp)
1167 lwsp++;
1168 }
1169
1170 if (*lwsp == '"')
1171 lwsp++;
1172 } else {
1173 /* normal word */
1174 while (*lwsp && !is_lwsp (*lwsp))
1175 lwsp++;
1176 }
1177
1178 len = lwsp - word;
1179 if (*linelen > 1 && (*linelen + len) > GMIME_FOLD_LEN) {
1180 linewrap (str, newline);
1181 *linelen = 1;
1182 }
1183
1184 g_string_append_len (str, word, len);
1185 *linelen += len;
1186
1187 word = lwsp;
1188 while (*word && is_lwsp (*word))
1189 word++;
1190
1191 if (*word && is_lwsp (*lwsp)) {
1192 g_string_append_c (str, ' ');
1193 (*linelen)++;
1194 }
1195 }
1196 }
1197
1198 static void
mailbox_to_string(InternetAddress * ia,GMimeFormatOptions * options,guint32 flags,size_t * linelen,GString * str)1199 mailbox_to_string (InternetAddress *ia, GMimeFormatOptions *options, guint32 flags, size_t *linelen, GString *str)
1200 {
1201 InternetAddressMailbox *mailbox = (InternetAddressMailbox *) ia;
1202 gboolean encode = flags & INTERNET_ADDRESS_ENCODE;
1203 gboolean fold = flags & INTERNET_ADDRESS_FOLD;
1204 const char *newline, *addr;
1205 char *name;
1206 size_t len;
1207
1208 newline = g_mime_format_options_get_newline (options);
1209
1210 addr = internet_address_mailbox_get_idn_addr (mailbox);
1211
1212 if (ia->name && *ia->name) {
1213 name = encoded_name (options, ia->name, encode, ia->charset);
1214 len = strlen (name);
1215
1216 if (fold && (*linelen + len) > GMIME_FOLD_LEN) {
1217 if (len > GMIME_FOLD_LEN) {
1218 /* we need to break up the name */
1219 append_folded_name (str, linelen, name, newline);
1220 } else {
1221 /* the name itself is short enough to fit on a single
1222 * line, but only if we write it on a line by itself */
1223 if (*linelen > 1) {
1224 linewrap (str, newline);
1225 *linelen = 1;
1226 }
1227
1228 g_string_append_len (str, name, len);
1229 *linelen += len;
1230 }
1231 } else {
1232 /* we can safely fit the name on this line */
1233 g_string_append_len (str, name, len);
1234 *linelen += len;
1235 }
1236
1237 g_free (name);
1238
1239 len = strlen (addr);
1240
1241 if (fold && (*linelen + len + 3) >= GMIME_FOLD_LEN) {
1242 g_string_append (str, newline);
1243 g_string_append_len (str, "\t<", 2);
1244 *linelen = 2;
1245 } else {
1246 g_string_append_len (str, " <", 2);
1247 *linelen += 2;
1248 }
1249
1250 g_string_append_len (str, addr, len);
1251 g_string_append_c (str, '>');
1252 *linelen += len + 1;
1253 } else {
1254 len = strlen (addr);
1255
1256 if (fold && (*linelen + len) > GMIME_FOLD_LEN) {
1257 linewrap (str, newline);
1258 *linelen = 1;
1259 }
1260
1261 g_string_append_len (str, addr, len);
1262 *linelen += len;
1263 }
1264 }
1265
1266 static void
_internet_address_list_to_string(const InternetAddressList * list,GMimeFormatOptions * options,guint32 flags,size_t * linelen,GString * str)1267 _internet_address_list_to_string (const InternetAddressList *list, GMimeFormatOptions *options, guint32 flags,
1268 size_t *linelen, GString *str)
1269 {
1270 InternetAddress *ia;
1271 guint i;
1272
1273 for (i = 0; i < list->array->len; i++) {
1274 ia = (InternetAddress *) list->array->pdata[i];
1275
1276 INTERNET_ADDRESS_GET_CLASS (ia)->to_string (ia, options, flags, linelen, str);
1277
1278 if (i + 1 < list->array->len) {
1279 g_string_append (str, ", ");
1280 *linelen += 2;
1281 }
1282 }
1283 }
1284
1285 static void
group_to_string(InternetAddress * ia,GMimeFormatOptions * options,guint32 flags,size_t * linelen,GString * str)1286 group_to_string (InternetAddress *ia, GMimeFormatOptions *options, guint32 flags, size_t *linelen, GString *str)
1287 {
1288 InternetAddressGroup *group = (InternetAddressGroup *) ia;
1289 gboolean encode = flags & INTERNET_ADDRESS_ENCODE;
1290 gboolean fold = flags & INTERNET_ADDRESS_FOLD;
1291 const char *newline;
1292 char *name = NULL;
1293 size_t len = 0;
1294
1295 newline = g_mime_format_options_get_newline (options);
1296
1297 if (ia->name != NULL) {
1298 name = encoded_name (options, ia->name, encode, ia->charset);
1299 len = strlen (name);
1300
1301 if (fold && *linelen > 1 && (*linelen + len + 1) > GMIME_FOLD_LEN) {
1302 linewrap (str, newline);
1303 *linelen = 1;
1304 }
1305
1306 g_string_append_len (str, name, len);
1307 }
1308
1309 g_string_append_len (str, ": ", 2);
1310 *linelen += len + 2;
1311 g_free (name);
1312
1313 _internet_address_list_to_string (group->members, options, flags, linelen, str);
1314 g_string_append_c (str, ';');
1315 *linelen += 1;
1316 }
1317
1318
1319 /**
1320 * internet_address_list_to_string:
1321 * @list: list of internet addresses
1322 * @options: (nullable): a #GMimeFormatOptions or %NULL
1323 * @encode: %TRUE if the address should be rfc2047 encoded
1324 *
1325 * Allocates a string buffer containing the rfc822 formatted addresses
1326 * in @list.
1327 *
1328 * Returns: (nullable): a string containing the list of addresses in rfc822
1329 * format or %NULL if no addresses are contained in the list.
1330 **/
1331 char *
internet_address_list_to_string(InternetAddressList * list,GMimeFormatOptions * options,gboolean encode)1332 internet_address_list_to_string (InternetAddressList *list, GMimeFormatOptions *options, gboolean encode)
1333 {
1334 guint32 flags = encode ? INTERNET_ADDRESS_ENCODE : 0;
1335 size_t linelen = 0;
1336 GString *str;
1337
1338 g_return_val_if_fail (IS_INTERNET_ADDRESS_LIST (list), NULL);
1339
1340 if (list->array->len == 0)
1341 return NULL;
1342
1343 str = g_string_new ("");
1344 _internet_address_list_to_string (list, options, flags, &linelen, str);
1345
1346 return g_string_free (str, FALSE);
1347 }
1348
1349
1350 /**
1351 * internet_address_list_encode:
1352 * @list: list of internet addresses
1353 * @options: (nullable): a #GMimeFormatOptions or %NULL
1354 * @str: string to write to
1355 *
1356 * Writes the rfc2047-encoded rfc822 formatted addresses in @list to
1357 * @str, folding appropriately.
1358 **/
1359 void
internet_address_list_encode(InternetAddressList * list,GMimeFormatOptions * options,GString * str)1360 internet_address_list_encode (InternetAddressList *list, GMimeFormatOptions *options, GString *str)
1361 {
1362 guint32 flags = INTERNET_ADDRESS_ENCODE | INTERNET_ADDRESS_FOLD;
1363 const char *newline;
1364 size_t linelen;
1365
1366 g_return_if_fail (IS_INTERNET_ADDRESS_LIST (list));
1367 g_return_if_fail (str != NULL);
1368
1369 newline = g_mime_format_options_get_newline (options);
1370 linelen = str->len;
1371
1372 _internet_address_list_to_string (list, options, flags, &linelen, str);
1373
1374 g_string_append (str, newline);
1375 }
1376
1377
1378 static char *
decode_name(GMimeParserOptions * options,const char * name,size_t len,const char ** charset,gint64 offset)1379 decode_name (GMimeParserOptions *options, const char *name, size_t len, const char **charset, gint64 offset)
1380 {
1381 char *value, *buf = NULL;
1382
1383 if (!g_utf8_validate (name, len, NULL)) {
1384 /* A (broken) mailer has sent us raw 8bit/multibyte text data... */
1385 buf = g_mime_utils_decode_8bit (options, name, len);
1386 } else {
1387 buf = g_strndup (name, len);
1388 }
1389
1390 /* decode the phrase */
1391 g_mime_utils_unquote_string (buf);
1392 value = _g_mime_utils_header_decode_phrase (options, buf, charset, offset);
1393 g_strstrip (value);
1394 g_free (buf);
1395
1396 return value;
1397 }
1398
1399
1400 typedef enum {
1401 ALLOW_MAILBOX = 1 << 0,
1402 ALLOW_GROUP = 1 << 1,
1403 ALLOW_ANY = ALLOW_MAILBOX | ALLOW_GROUP
1404 } AddressParserFlags;
1405
1406 static gboolean
decode_route(const char ** in)1407 decode_route (const char **in)
1408 {
1409 const char *inptr = *in;
1410 GString *route;
1411
1412 route = g_string_new ("");
1413
1414 do {
1415 inptr++;
1416
1417 g_string_append_c (route, '@');
1418 if (!decode_domain (&inptr, route)) {
1419 g_string_free (route, TRUE);
1420 goto error;
1421 }
1422
1423 skip_cfws (&inptr);
1424 if (*inptr == ',') {
1425 g_string_append_c (route, ',');
1426 inptr++;
1427 skip_cfws (&inptr);
1428
1429 /* obs-domain-lists allow commas with nothing between them... */
1430 while (*inptr == ',') {
1431 inptr++;
1432 skip_cfws (&inptr);
1433 }
1434 }
1435 } while (*inptr == '@');
1436
1437 g_string_free (route, TRUE);
1438 skip_cfws (&inptr);
1439
1440 if (*inptr != ':') {
1441 w(g_warning ("Invalid route domain-list, missing ':': %.*s", inptr - start, start));
1442 goto error;
1443 }
1444
1445 *in = inptr;
1446
1447 return TRUE;
1448
1449 error:
1450
1451 while (*inptr && *inptr != ':' && *inptr != '>')
1452 inptr++;
1453
1454 *in = inptr;
1455
1456 return FALSE;
1457 }
1458
1459 static gboolean
localpart_parse(GString * localpart,const char ** in)1460 localpart_parse (GString *localpart, const char **in)
1461 {
1462 const char *inptr = *in;
1463 const char *word;
1464
1465 do {
1466 word = inptr;
1467
1468 if (!skip_word (&inptr))
1469 goto error;
1470
1471 if (!g_utf8_validate (word, (size_t) (inptr - word), NULL))
1472 goto error;
1473
1474 g_string_append_len (localpart, word, (size_t) (inptr - word));
1475
1476 if (!skip_cfws (&inptr))
1477 goto error;
1478
1479 if (*inptr != '.')
1480 break;
1481
1482 g_string_append_c (localpart, *inptr++);
1483
1484 if (!skip_cfws (&inptr))
1485 goto error;
1486
1487 if (*inptr == '\0')
1488 goto error;
1489 } while (TRUE);
1490
1491 *in = inptr;
1492
1493 return TRUE;
1494
1495 error:
1496 *in = inptr;
1497
1498 return FALSE;
1499 }
1500
1501 #define COMMA_GREATER_THAN_OR_SEMICOLON ",>;"
1502
1503 static gboolean
dotatom_parse(GString * str,const char ** in,const char * sentinels)1504 dotatom_parse (GString *str, const char **in, const char *sentinels)
1505 {
1506 const char *atom, *comment;
1507 const char *inptr = *in;
1508 GString *domain = str;
1509
1510 do {
1511 if (!is_atom (*inptr))
1512 goto error;
1513
1514 atom = inptr;
1515 while (is_atom (*inptr))
1516 inptr++;
1517
1518 if (!g_utf8_validate (atom, (size_t) (inptr - atom), NULL))
1519 goto error;
1520
1521 #if LIBIDN
1522 if (domain == str && !strncmp (atom, "xn--", 4)) {
1523 /* from here on out, we'll use a temp domain buffer so that
1524 * we can decode it once we're done parsing it */
1525 domain = g_string_new ("");
1526 }
1527 #endif
1528
1529 g_string_append_len (domain, atom, (size_t) (inptr - atom));
1530
1531 comment = inptr;
1532 if (!skip_cfws (&inptr))
1533 goto error;
1534
1535 if (*inptr != '.') {
1536 inptr = comment;
1537 break;
1538 }
1539
1540 /* skip over the '.' */
1541 inptr++;
1542
1543 if (!skip_cfws (&inptr))
1544 goto error;
1545
1546 /* allow domains to end with a '.', but strip it off */
1547 if (*inptr == '\0' || strchr (sentinels, *inptr))
1548 break;
1549
1550 g_string_append_c (domain, '.');
1551 } while (TRUE);
1552
1553 #ifdef LIBIDN
1554 if (domain != str) {
1555 char *unicode;
1556
1557 if (idn2_to_unicode_8z8z (domain->str, &unicode, 0) == IDN2_OK) {
1558 g_string_append (str, unicode);
1559 free (unicode);
1560 } else {
1561 g_string_append_len (str, domain->str, domain->len);
1562 }
1563
1564 g_string_free (domain, TRUE);
1565 }
1566 #endif
1567
1568 *in = inptr;
1569
1570 return TRUE;
1571
1572 error:
1573 #ifdef LIBIDN
1574 if (domain != str) {
1575 g_string_append_len (str, domain->str, domain->len);
1576 g_string_free (domain, TRUE);
1577 }
1578 #endif
1579
1580 *in = inptr;
1581
1582 return FALSE;
1583 }
1584
1585 static gboolean
domain_literal_parse(GString * str,const char ** in)1586 domain_literal_parse (GString *str, const char **in)
1587 {
1588 const char *inptr = *in;
1589
1590 g_string_append_c (str, '[');
1591 inptr++;
1592
1593 skip_lwsp (&inptr);
1594
1595 do {
1596 while (*inptr && is_dtext (*inptr))
1597 g_string_append_c (str, *inptr++);
1598
1599 skip_lwsp (&inptr);
1600
1601 if (*inptr == '\0')
1602 goto error;
1603
1604 if (*inptr == ']')
1605 break;
1606
1607 if (!is_dtext (*inptr))
1608 goto error;
1609 } while (TRUE);
1610
1611 g_string_append_c (str, ']');
1612 *in = inptr + 1;
1613
1614 return TRUE;
1615
1616 error:
1617 *in = inptr;
1618
1619 return FALSE;
1620 }
1621
1622 static gboolean
domain_parse(GString * str,const char ** in,const char * sentinels)1623 domain_parse (GString *str, const char **in, const char *sentinels)
1624 {
1625 if (**in == '[')
1626 return domain_literal_parse (str, in);
1627
1628 return dotatom_parse (str, in, sentinels);
1629 }
1630
1631 static gboolean
addrspec_parse(const char ** in,const char * sentinels,char ** addrspec,int * at)1632 addrspec_parse (const char **in, const char *sentinels, char **addrspec, int *at)
1633 {
1634 const char *inptr = *in;
1635 GString *str;
1636
1637 str = g_string_new ("");
1638
1639 if (!localpart_parse (str, &inptr))
1640 goto error;
1641
1642 if (*inptr == '\0' || strchr (sentinels, *inptr)) {
1643 *addrspec = g_string_free (str, FALSE);
1644 *in = inptr;
1645 *at = -1;
1646 return TRUE;
1647 }
1648
1649 if (*inptr != '@')
1650 goto error;
1651
1652 *at = str->len;
1653 g_string_append_c (str, *inptr++);
1654
1655 if (*inptr == '\0')
1656 goto error;
1657
1658 if (!skip_cfws (&inptr))
1659 goto error;
1660
1661 if (*inptr == '\0')
1662 goto error;
1663
1664 if (!domain_parse (str, &inptr, sentinels))
1665 goto error;
1666
1667 *addrspec = g_string_free (str, FALSE);
1668 *in = inptr;
1669
1670 return TRUE;
1671
1672 error:
1673 g_string_free (str, TRUE);
1674 *addrspec = NULL;
1675 *in = inptr;
1676 *at = -1;
1677
1678 return FALSE;
1679 }
1680
1681 // TODO: rename to angleaddr_parse??
1682 static gboolean
mailbox_parse(GMimeParserOptions * options,const char ** in,const char * name,InternetAddress ** address)1683 mailbox_parse (GMimeParserOptions *options, const char **in, const char *name, InternetAddress **address)
1684 {
1685 GMimeRfcComplianceMode mode = g_mime_parser_options_get_address_compliance_mode (options);
1686 const char *inptr = *in;
1687 char *addrspec = NULL;
1688 int at;
1689
1690 /* skip over the '<' */
1691 inptr++;
1692
1693 /* Note: check for excessive angle brackets like the example described in section 7.1.2 of rfc7103... */
1694 if (*inptr == '<') {
1695 if (mode != GMIME_RFC_COMPLIANCE_LOOSE)
1696 goto error;
1697
1698 do {
1699 inptr++;
1700 } while (*inptr == '<');
1701 }
1702
1703 if (*inptr == '\0')
1704 goto error;
1705
1706 if (!skip_cfws (&inptr))
1707 goto error;
1708
1709 if (*inptr == '@') {
1710 if (!decode_route (&inptr))
1711 goto error;
1712
1713 if (*inptr != ':')
1714 goto error;
1715
1716 inptr++;
1717
1718 if (!skip_cfws (&inptr))
1719 goto error;
1720 }
1721
1722 // Note: The only syntactically correct sentinel token here is the '>', but alas... to deal with the first
1723 // example in section 7.1.5 of rfc7103, we need to at least handle ',' as a sentinel and might as well handle
1724 // ';' as well in case the mailbox is within a group address.
1725 //
1726 // Example: <third@example.net, fourth@example.net>
1727 if (!addrspec_parse (&inptr, COMMA_GREATER_THAN_OR_SEMICOLON, &addrspec, &at))
1728 goto error;
1729
1730 if (!skip_cfws (&inptr))
1731 goto error;
1732
1733 if (*inptr != '>') {
1734 if (mode != GMIME_RFC_COMPLIANCE_LOOSE)
1735 goto error;
1736 } else {
1737 /* skip over the '>' */
1738 inptr++;
1739
1740 /* Note: check for excessive angle brackets like the example described in section 7.1.2 of rfc7103... */
1741 if (*inptr == '>') {
1742 if (mode != GMIME_RFC_COMPLIANCE_LOOSE)
1743 goto error;
1744
1745 do {
1746 inptr++;
1747 } while (*inptr == '>');
1748 }
1749 }
1750
1751 *address = _internet_address_mailbox_new (name, addrspec, at);
1752 g_free (addrspec);
1753 *in = inptr;
1754
1755 return TRUE;
1756
1757 error:
1758 g_free (addrspec);
1759 *address = NULL;
1760 *in = inptr;
1761
1762 return FALSE;
1763 }
1764
1765 static gboolean address_list_parse (InternetAddressList *list, GMimeParserOptions *options, const char **in, gboolean is_group, gint64 offset);
1766
1767 static gboolean
group_parse(InternetAddressGroup * group,GMimeParserOptions * options,const char ** in,gint64 offset)1768 group_parse (InternetAddressGroup *group, GMimeParserOptions *options, const char **in, gint64 offset)
1769 {
1770 const char *inptr = *in;
1771
1772 /* skip over the ':' */
1773 inptr++;
1774
1775 /* ignore superfluous colons (and whitespace) */
1776 while (*inptr == ':' || is_lwsp (*inptr))
1777 inptr++;
1778
1779 if (*inptr != '\0') {
1780 address_list_parse (group->members, options, &inptr, TRUE, offset);
1781
1782 if (*inptr != ';') {
1783 while (*inptr && *inptr != ';')
1784 inptr++;
1785 } else {
1786 inptr++;
1787 }
1788 }
1789
1790 *in = inptr;
1791
1792 return TRUE;
1793 }
1794
1795 static gboolean
address_parse(GMimeParserOptions * options,AddressParserFlags flags,const char ** in,const char ** charset,InternetAddress ** address,gint64 offset)1796 address_parse (GMimeParserOptions *options, AddressParserFlags flags, const char **in, const char **charset, InternetAddress **address, gint64 offset)
1797 {
1798 GMimeRfcComplianceMode mode = g_mime_parser_options_get_address_compliance_mode (options);
1799 int min_words = g_mime_parser_options_get_allow_addresses_without_domain (options) ? 1 : 0;
1800 gboolean trim_leading_quote = FALSE;
1801 const char *inptr = *in;
1802 const char *start;
1803 size_t length;
1804 int words = 0;
1805
1806 if (!skip_cfws (&inptr) || *inptr == '\0')
1807 goto error;
1808
1809 /* keep track of the start & length of the phrase */
1810 start = inptr;
1811 length = 0;
1812
1813 while (*inptr) {
1814 if (mode != GMIME_RFC_COMPLIANCE_LOOSE) {
1815 if (!skip_word (&inptr))
1816 break;
1817 } else if (*inptr == '"') {
1818 const char *qstring = inptr;
1819
1820 if (!skip_quoted (&inptr)) {
1821 inptr = qstring + 1;
1822
1823 skip_lwsp (&inptr);
1824
1825 if (!skip_atom (&inptr))
1826 break;
1827
1828 if (start == qstring)
1829 trim_leading_quote = TRUE;
1830 }
1831 } else {
1832 if (!skip_atom (&inptr))
1833 break;
1834 }
1835
1836 length = (size_t) (inptr - start);
1837
1838 do {
1839 if (!skip_cfws (&inptr))
1840 goto error;
1841
1842 /* Note: some clients don't quote dots in the name */
1843 if (*inptr != '.')
1844 break;
1845
1846 inptr++;
1847
1848 length = (size_t) (inptr - start);
1849 } while (TRUE);
1850
1851 words++;
1852
1853 /* Note: some clients don't quote commas in the name */
1854 if (*inptr == ',' && words > min_words) {
1855 inptr++;
1856
1857 length = (size_t) (inptr - start);
1858
1859 if (!skip_cfws (&inptr))
1860 goto error;
1861 }
1862 }
1863
1864 if (!skip_cfws (&inptr))
1865 goto error;
1866
1867 // specials = "(" / ")" / "<" / ">" / "@" ; Must be in quoted-
1868 // / "," / ";" / ":" / "\" / <"> ; string, to use
1869 // / "." / "[" / "]" ; within a word.
1870
1871 if (*inptr == '\0' || *inptr == ',' || *inptr == '>' || *inptr == ';') {
1872 /* we've completely gobbled up an addr-spec w/o a domain */
1873 char sentinel = *inptr != '\0' ? *inptr : ',';
1874 char sentinels[2] = { sentinel, 0 };
1875 char *name, *addrspec;
1876 int at;
1877
1878 /* rewind back to the beginning of the local-part */
1879 inptr = start;
1880
1881 if (!(flags & ALLOW_MAILBOX))
1882 goto error;
1883
1884 if (!addrspec_parse (&inptr, sentinels, &addrspec, &at))
1885 goto error;
1886
1887 skip_lwsp (&inptr);
1888
1889 if (*inptr == '(') {
1890 const char *comment = inptr;
1891
1892 if (!skip_comment (&inptr))
1893 goto error;
1894
1895 comment++;
1896
1897 name = decode_name (options, comment, (size_t) ((inptr - 1) - comment), charset, offset);
1898 } else {
1899 name = g_strdup ("");
1900 }
1901
1902 if (*inptr == '>') {
1903 if (mode != GMIME_RFC_COMPLIANCE_LOOSE)
1904 goto error;
1905
1906 inptr++;
1907 }
1908
1909 *address = _internet_address_mailbox_new (name, addrspec, at);
1910 g_free (addrspec);
1911 g_free (name);
1912 *in = inptr;
1913
1914 return TRUE;
1915 }
1916
1917 if (*inptr == ':') {
1918 /* rfc2822 group address */
1919 InternetAddressGroup *group;
1920 const char *phrase = start;
1921 gboolean retval;
1922 char *name;
1923
1924 if (!(flags & ALLOW_GROUP))
1925 goto error;
1926
1927 if (trim_leading_quote) {
1928 phrase++;
1929 length--;
1930 }
1931
1932 if (length > 0) {
1933 name = decode_name (options, phrase, length, charset, offset);
1934 } else {
1935 name = g_strdup ("");
1936 }
1937
1938 group = (InternetAddressGroup *) internet_address_group_new (name);
1939 *address = (InternetAddress *) group;
1940 g_free (name);
1941
1942 retval = group_parse (group, options, &inptr, offset);
1943 *in = inptr;
1944
1945 return retval;
1946 }
1947
1948 if (!(flags & ALLOW_MAILBOX))
1949 goto error;
1950
1951 if (*inptr == '@') {
1952 /* we're either in the middle of an addr-spec token or we completely gobbled up
1953 * an addr-spec w/o a domain */
1954 char *name, *addrspec;
1955 int at;
1956
1957 /* rewind back to the beginning of the local-part */
1958 inptr = start;
1959
1960 if (!addrspec_parse (&inptr, COMMA_GREATER_THAN_OR_SEMICOLON, &addrspec, &at))
1961 goto error;
1962
1963 skip_lwsp (&inptr);
1964
1965 if (*inptr == '(') {
1966 const char *comment = inptr;
1967
1968 if (!skip_comment (&inptr))
1969 goto error;
1970
1971 comment++;
1972
1973 name = decode_name (options, comment, (size_t) ((inptr - 1) - comment), charset, offset);
1974 } else {
1975 name = g_strdup ("");
1976 }
1977
1978 if (!skip_cfws (&inptr)) {
1979 g_free (addrspec);
1980 g_free (name);
1981 goto error;
1982 }
1983
1984 if (*inptr == '\0') {
1985 *address = _internet_address_mailbox_new (name, addrspec, at);
1986 g_free (addrspec);
1987 g_free (name);
1988 *in = inptr;
1989
1990 return TRUE;
1991 }
1992
1993 if (*inptr == '<') {
1994 /* We have an address like "user@example.com <user@example.com>"; i.e. the name
1995 * is an unquoted string with an '@'. */
1996 const char *end;
1997
1998 if (mode != GMIME_RFC_COMPLIANCE_LOOSE)
1999 goto error;
2000
2001 end = inptr;
2002 while (end > start && is_lwsp (*(end - 1)))
2003 end--;
2004
2005 length = (size_t) (end - start);
2006 g_free (addrspec);
2007 g_free (name);
2008
2009 /* fall through to the rfc822 angle-addr token case... */
2010 } else {
2011 /* Note: since there was no '<', there should not be a '>'... but we handle it
2012 * anyway in order to deal with the second Unbalanced Angle Brackets example in
2013 * section 7.1.3: second@example.org> */
2014 if (*inptr == '>') {
2015 if (mode != GMIME_RFC_COMPLIANCE_LOOSE)
2016 goto error;
2017
2018 inptr++;
2019 }
2020
2021 *address = _internet_address_mailbox_new (name, addrspec, at);
2022 g_free (addrspec);
2023 g_free (name);
2024 *in = inptr;
2025
2026 return TRUE;
2027 }
2028 }
2029
2030 if (*inptr == '<') {
2031 /* rfc2822 angle-addr token */
2032 const char *phrase = start;
2033 gboolean retval;
2034 char *name;
2035
2036 if (trim_leading_quote) {
2037 phrase++;
2038 length--;
2039 }
2040
2041 if (length > 0) {
2042 name = decode_name (options, phrase, length, charset, offset);
2043 } else {
2044 name = g_strdup ("");
2045 }
2046
2047 retval = mailbox_parse (options, &inptr, name, address);
2048 g_free (name);
2049 *in = inptr;
2050
2051 return retval;
2052 }
2053
2054 error:
2055 if (g_mime_parser_options_get_warning_callback (options) != NULL)
2056 _g_mime_parser_options_warn (options, offset, GMIME_WARN_INVALID_ADDRESS_LIST, *in);
2057
2058 *address = NULL;
2059 *in = inptr;
2060
2061 return FALSE;
2062 }
2063
2064 static gboolean
address_list_parse(InternetAddressList * list,GMimeParserOptions * options,const char ** in,gboolean is_group,gint64 offset)2065 address_list_parse (InternetAddressList *list, GMimeParserOptions *options, const char **in, gboolean is_group, gint64 offset)
2066 {
2067 gboolean can_warn = g_mime_parser_options_get_warning_callback (options) != NULL;
2068 InternetAddress *address;
2069 const char *charset;
2070 const char *inptr;
2071
2072 if (!skip_cfws (in))
2073 return FALSE;
2074
2075 inptr = *in;
2076
2077 if (*inptr == '\0')
2078 return FALSE;
2079
2080 while (*inptr) {
2081 gboolean separator_between_addrs = FALSE;
2082
2083 if (is_group && *inptr == ';')
2084 break;
2085
2086 charset = NULL;
2087
2088 if (!address_parse (options, ALLOW_ANY, &inptr, &charset, &address, offset)) {
2089 /* skip this address... */
2090 while (*inptr && *inptr != ',' && (!is_group || *inptr != ';'))
2091 inptr++;
2092 } else {
2093 _internet_address_list_add (list, address);
2094
2095 if (charset)
2096 address->charset = g_strdup (charset);
2097
2098 if (INTERNET_ADDRESS_IS_GROUP(address))
2099 separator_between_addrs = TRUE;
2100 }
2101
2102 /* Note: we loop here in case there are any null addresses between commas */
2103 do {
2104 if (!skip_cfws (&inptr)) {
2105 *in = inptr;
2106
2107 return FALSE;
2108 }
2109
2110 if (*inptr != ',')
2111 break;
2112
2113 separator_between_addrs = TRUE;
2114 inptr++;
2115 } while (TRUE);
2116
2117 if (can_warn && !(separator_between_addrs || (*inptr == '\0') || (is_group && *inptr == ';')))
2118 _g_mime_parser_options_warn (options, offset, GMIME_WARN_INVALID_ADDRESS_LIST, *in);
2119 }
2120
2121 *in = inptr;
2122
2123 return TRUE;
2124 }
2125
2126
2127 /**
2128 * internet_address_list_parse:
2129 * @options: (nullable): a #GMimeParserOptions or %NULL
2130 * @str: a string containing internet addresses
2131 *
2132 * Construct a list of internet addresses from the given string.
2133 *
2134 * Returns: (nullable) (transfer full): a #InternetAddressList or %NULL if the
2135 * input string does not contain any addresses.
2136 **/
2137 InternetAddressList *
internet_address_list_parse(GMimeParserOptions * options,const char * str)2138 internet_address_list_parse (GMimeParserOptions *options, const char *str)
2139 {
2140 return _internet_address_list_parse (options, str, -1);
2141 }
2142
2143 InternetAddressList *
_internet_address_list_parse(GMimeParserOptions * options,const char * str,gint64 offset)2144 _internet_address_list_parse (GMimeParserOptions *options, const char *str, gint64 offset)
2145 {
2146 InternetAddressList *list;
2147 const char *inptr = str;
2148
2149 g_return_val_if_fail (str != NULL, NULL);
2150
2151 list = internet_address_list_new ();
2152 if (!address_list_parse (list, options, &inptr, FALSE, offset) || list->array->len == 0) {
2153 g_object_unref (list);
2154 return NULL;
2155 }
2156
2157 return list;
2158 }
2159