1 #if !defined(lint) && !defined(DOS)
2 static char rcsid
[] = "$Id: takeaddr.c 1012 2008-03-26 00:44:22Z hubert@u.washington.edu $";
6 * ========================================================================
7 * Copyright 2006-2007 University of Washington
8 * Copyright 2013-2020 Eduardo Chappa
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
14 * http://www.apache.org/licenses/LICENSE-2.0
16 * ========================================================================
19 /*======================================================================
21 Mostly support for Take Address command.
25 #include "../pith/headers.h"
26 #include "../pith/takeaddr.h"
27 #include "../pith/conf.h"
28 #include "../pith/bldaddr.h"
29 #include "../pith/adrbklib.h"
30 #include "../pith/copyaddr.h"
31 #include "../pith/addrstring.h"
32 #include "../pith/status.h"
33 #include "../pith/mailview.h"
34 #include "../pith/reply.h"
35 #include "../pith/url.h"
36 #include "../pith/mailpart.h"
37 #include "../pith/sequence.h"
38 #include "../pith/stream.h"
39 #include "../pith/busy.h"
40 #include "../pith/ablookup.h"
41 #include "../pith/list.h"
44 static char *fakedomain
= "@";
45 long msgno_for_pico_callback
;
46 BODY
*body_for_pico_callback
= NULL
;
47 ENVELOPE
*env_for_pico_callback
= NULL
;
51 * Previous selectable TA line.
52 * skips over the elements with skip_it set
55 pre_sel_taline(TA_S
*current
)
63 while(p
&& p
->skip_it
)
71 * Previous TA line, selectable or just printable.
72 * skips over the elements with skip_it set
75 pre_taline(TA_S
*current
)
83 while(p
&& (p
->skip_it
&& !p
->print
))
91 * Next selectable TA line.
92 * skips over the elements with skip_it set
95 next_sel_taline(TA_S
*current
)
103 while(p
&& p
->skip_it
)
111 * Next TA line, including print only lines.
112 * skips over the elements with skip_it set unless they are print lines
115 next_taline(TA_S
*current
)
123 while(p
&& (p
->skip_it
&& !p
->print
))
131 * Mark all of the addresses with a check.
133 * Args: f_line -- the first ta line
135 * Returns the number of lines checked.
138 ta_mark_all(TA_S
*f_line
)
141 int how_many_selected
= 0;
143 for(ctmp
= f_line
; ctmp
; ctmp
= next_sel_taline(ctmp
)){
148 return(how_many_selected
);
153 * Does the takeaddr list consist of a single address?
155 * Args: f_line -- the first ta line
157 * Returns 1 if only one address and 0 otherwise.
160 is_talist_of_one(TA_S
*f_line
)
165 /* there is at least one, see if there are two */
166 if(next_sel_taline(f_line
) != NULL
)
174 * Turn off all of the check marks.
176 * Args: f_line -- the first ta line
178 * Returns the number of lines checked (0).
181 ta_unmark_all(TA_S
*f_line
)
185 for(ctmp
= f_line
; ctmp
; ctmp
= ctmp
->next
)
193 * new_taline - create new TA_S, zero it out, and insert it after current.
194 * NOTE current gets set to the new TA_S, too!
197 new_taline(TA_S
**current
)
201 p
= (TA_S
*)fs_get(sizeof(TA_S
));
202 memset((void *)p
, 0, sizeof(TA_S
));
205 p
->next
= (*current
)->next
;
206 (*current
)->next
= p
;
220 free_taline(TA_S
**p
)
224 mail_free_address(&(*p
)->addr
);
227 fs_give((void **)&(*p
)->strvalue
);
230 fs_give((void **)&(*p
)->nickname
);
233 fs_give((void **)&(*p
)->fullname
);
236 fs_give((void **)&(*p
)->fcc
);
239 fs_give((void **)&(*p
)->comment
);
242 (*p
)->prev
->next
= (*p
)->next
;
245 (*p
)->next
->prev
= (*p
)->prev
;
253 free_talines(TA_S
**ta_list
)
255 TA_S
*current
, *ctmp
;
257 if(ta_list
&& (current
= (*ta_list
))){
259 current
= current
->prev
;
262 ctmp
= current
->next
;
263 free_taline(¤t
);
273 free_ltline(LINES_TO_TAKE
**p
)
277 fs_give((void **)&(*p
)->printval
);
280 fs_give((void **)&(*p
)->exportval
);
283 (*p
)->prev
->next
= (*p
)->next
;
286 (*p
)->next
->prev
= (*p
)->prev
;
294 free_ltlines(LINES_TO_TAKE
**lt_list
)
296 LINES_TO_TAKE
*current
, *ctmp
;
298 if(lt_list
&& (current
= (*lt_list
))){
300 current
= current
->prev
;
303 ctmp
= current
->next
;
304 free_ltline(¤t
);
314 * Return the first selectable TakeAddr line.
316 * Args: q -- any line in the list
319 first_sel_taline(TA_S
*q
)
327 /* back up to the head of the list */
330 q
= pre_sel_taline(q
);
334 * If first->skip_it, that means we were already past the first
335 * legitimate line, so we have to look in the other direction.
338 first
= next_sel_taline(first
);
345 * Return the last selectable TakeAddr line.
347 * Args: q -- any line in the list
350 last_sel_taline(TA_S
*q
)
358 /* go to the end of the list */
361 q
= next_sel_taline(q
);
365 * If last->skip_it, that means we were already past the last
366 * legitimate line, so we have to look in the other direction.
369 last
= pre_sel_taline(last
);
376 * Return the first TakeAddr line, selectable or just printable.
378 * Args: q -- any line in the list
381 first_taline(TA_S
*q
)
389 /* back up to the head of the list */
396 * If first->skip_it, that means we were already past the first
397 * legitimate line, so we have to look in the other direction.
399 if(first
->skip_it
&& !first
->print
)
400 first
= next_taline(first
);
407 * Find the first TakeAddr line which is checked, beginning with the
410 * Args: head -- A passed in TakeAddr line, usually will be the first
412 * Result: returns a pointer to the first checked line.
413 * NULL if no such line
416 first_checked(TA_S
*head
)
422 for(p
= head
; p
; p
= next_sel_taline(p
))
423 if(p
->checked
&& !p
->skip_it
)
431 * Form a list of strings which are addresses to go in a list.
432 * These are entries in a list, so can be full rfc822 addresses.
433 * The strings are allocated here.
435 * Args: head -- A passed in TakeAddr line, usually will be the first
437 * Result: returns an allocated array of pointers to allocated strings
440 list_of_checked(TA_S
*head
)
447 /* first count them */
448 for(p
= head
, count
= 0; p
; p
= next_sel_taline(p
)){
449 if(p
->checked
&& !p
->skip_it
){
452 * Remove fullname, fcc, comment, and nickname since not
453 * appropriate for list values.
456 fs_give((void **)&p
->fullname
);
458 fs_give((void **)&p
->fcc
);
460 fs_give((void **)&p
->comment
);
462 fs_give((void **)&p
->nickname
);
464 for(a
= p
->addr
; a
; a
= a
->next
)
469 * Don't even attempt to include bogus addresses in
470 * the list. If the user wants to get at those, they
471 * have to try taking only that single address.
473 if(p
->addr
&& p
->addr
->host
&& p
->addr
->host
[0] == '.')
481 /* allocate pointers */
482 list
= (char **)fs_get((count
+ 1) * sizeof(char *));
483 memset((void *)list
, 0, (count
+ 1) * sizeof(char *));
487 /* allocate and point to address strings */
488 for(p
= head
; p
; p
= next_sel_taline(p
)){
489 if(p
->checked
&& !p
->skip_it
){
491 for(a
= p
->addr
; a
; a
= a
->next
){
499 bufp
= (char *) fs_get(len
* sizeof(char));
500 *cur
++ = cpystr(addr_string(a
, bufp
, len
));
502 fs_give((void **)&bufp
);
505 *cur
++ = cpystr(p
->strvalue
);
512 /* jpf: This used to be the guts of cmd_take_addr, but I made this
513 * function so I could generalize with WP. It still has some display
514 * stuff that we need make sure not to do when in WP mode.
516 * Set things up for when we take addresses
518 * Args: ps -- pine state
519 * msgmap -- the MessageMap
520 * ta_ret -- the take addr lines
521 * selected_num -- the number of selectable addresses
522 * flags -- takeaddr flags, like whether or not
523 * we're doing aggs, and whether to do prompts
525 * Returns: -1 on "failure"
528 set_up_takeaddr(int cmd
, struct pine
*ps
, MSGNO_S
*msgmap
, TA_S
**ta_ret
,
529 int *selected_num
, int flags
, int (*att_addr_f
)(TA_S
*, int))
533 int how_many_selected
= 0,
536 special_processing
= 0,
538 TA_S
*current
= NULL
,
542 *special_body
= NULL
,
545 dprint((2, "\n - taking address into address book - \n"));
547 if(!(cmd
== 'a' || cmd
== 'e'))
550 if((flags
& TA_AGG
) && !pseudo_selected(ps_global
->mail_stream
, msgmap
))
553 if(mn_get_total(msgmap
) > 0 && mn_total_cur(msgmap
) == 1)
554 special_processing
= 1;
559 /* this is a non-selectable label */
560 current
= fill_in_ta(¤t
, (ADDRESS
*) NULL
, 0,
561 /* TRANSLATORS: This is a heading describing some addresses the
562 user will have the chance to choose from. */
563 _(" These entries are taken from the attachments "));
564 prev_comment_line
= current
;
567 * Add addresses from special attachments for each message.
570 for(i
= mn_first_cur(msgmap
); i
> 0L; i
= mn_next_cur(msgmap
)){
571 env
= pine_mail_fetchstructure(ps
->mail_stream
, mn_m2raw(msgmap
, i
), &body
);
573 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
574 _("Can't take address into address book. Error accessing folder"));
579 added
+= process_vcard_atts(ps
->mail_stream
, mn_m2raw(msgmap
, i
),
580 body
, body
, NULL
, ¤t
);
586 && (*att_addr_f
)(current
, added
) <= 0)
589 if(!(flags
& TA_NOPROMPT
))
590 we_cancel
= busy_cue(NULL
, NULL
, 1);
593 * add a comment line to separate attachment address lines
594 * from header address lines
597 current
= fill_in_ta(¤t
, (ADDRESS
*) NULL
, 0,
598 /* TRANSLATORS: msg is message */
599 _(" These entries are taken from the msg headers "));
600 prev_comment_line
= current
;
601 how_many_selected
+= added
;
604 else{ /* turn off header comment, and no separator comment */
605 prev_comment_line
->print
= 0;
606 prev_comment_line
= NULL
;
610 * Add addresses from headers of messages.
612 for(i
= mn_first_cur(msgmap
); i
> 0L; i
= mn_next_cur(msgmap
)){
614 if(special_processing
)
615 body_h
= &special_body
;
619 env
= pine_mail_fetchstructure(ps
->mail_stream
, mn_m2raw(msgmap
, i
),
625 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
626 _("Can't take address into address book. Error accessing folder"));
631 added
= add_addresses_to_talist(ps
, i
, "from", ¤t
,
632 env
->from
, select_froms
);
634 how_many_selected
+= added
;
636 if(!address_is_same(env
->from
, env
->reply_to
))
637 (void)add_addresses_to_talist(ps
, i
, "reply-to", ¤t
,
640 if(!address_is_same(env
->from
, env
->sender
))
641 (void)add_addresses_to_talist(ps
, i
, "sender", ¤t
,
644 (void)add_addresses_to_talist(ps
, i
, "to", ¤t
, env
->to
, 0);
645 (void)add_addresses_to_talist(ps
, i
, "cc", ¤t
, env
->cc
, 0);
646 (void)add_addresses_to_talist(ps
, i
, "bcc", ¤t
, env
->bcc
, 0);
650 * Check to see if we added an explanatory line about the
651 * header addresses but no header addresses. If so, remove the
654 if(prev_comment_line
){
656 eliminate_dups_and_us(first_sel_taline(current
));
657 for(ta
= prev_comment_line
->next
; ta
; ta
= ta
->next
)
661 /* all entries were skip_it entries, turn off print */
663 prev_comment_line
->print
= 0;
664 prev_comment_line
= NULL
;
669 * If there's only one message we let the user view it using ^T
670 * from the header editor.
672 if(!(flags
& TA_NOPROMPT
) && special_processing
&& env
&& special_body
){
673 msgno_for_pico_callback
= mn_m2raw(msgmap
, mn_first_cur(msgmap
));
674 body_for_pico_callback
= special_body
;
675 env_for_pico_callback
= env
;
678 env_for_pico_callback
= NULL
;
679 body_for_pico_callback
= NULL
;
682 current
= fill_in_ta(¤t
, (ADDRESS
*)NULL
, 0,
683 _(" Below this line are some possibilities taken from the text of the msg "));
684 prev_comment_line
= current
;
687 * Add addresses from the text of the body.
690 for(i
= mn_first_cur(msgmap
); i
> 0L; i
= mn_next_cur(msgmap
)){
693 env
= pine_mail_fetchstructure(ps
->mail_stream
, mn_m2raw(msgmap
, i
),
696 added
+= grab_addrs_from_body(ps
->mail_stream
,
702 prev_comment_line
->print
= 0;
703 prev_comment_line
= NULL
;
707 * Check to see if all we added are dups.
708 * If so, remove the comment line.
710 if(prev_comment_line
){
711 how_many_selected
-= eliminate_dups_and_us(first_sel_taline(current
));
712 for(ta
= prev_comment_line
->next
; ta
; ta
= ta
->next
)
716 /* all entries were skip_it entries, turn off print */
718 prev_comment_line
->print
= 0;
725 env_for_pico_callback
= NULL
;
726 body_for_pico_callback
= NULL
;
728 ps
->mangled_screen
= 1;
731 restore_selected(msgmap
);
736 *selected_num
= how_many_selected
;
743 convert_ta_to_lines(TA_S
*ta_list
, LINES_TO_TAKE
**old_current
)
747 LINES_TO_TAKE
*new_current
;
748 char *exportval
, *printval
;
751 for(ta
= first_sel_taline(ta_list
);
753 ta
= next_sel_taline(ta
)){
759 fs_give((void **)&ta
->fullname
);
761 fs_give((void **)&ta
->fcc
);
763 fs_give((void **)&ta
->comment
);
765 fs_give((void **)&ta
->nickname
);
767 else if(ta
->addr
&& ta
->addr
->host
&& ta
->addr
->host
[0] == '.')
771 for(a
= ta
->addr
; a
; a
= a
->next
){
774 if(a
->host
&& a
->host
[0] == '.')
780 exportval
= cpystr(simple_addr_string(a
, tmp_20k_buf
,
782 if(!exportval
|| !exportval
[0]){
784 fs_give((void **)&exportval
);
790 printval
= addr_list_string(a
, NULL
, 0);
791 if(!printval
|| !printval
[0]){
793 fs_give((void **)&printval
);
795 printval
= cpystr(exportval
);
798 new_current
= new_ltline(old_current
);
799 new_current
->flags
= LT_NONE
;
801 new_current
->printval
= printval
;
802 new_current
->exportval
= exportval
;
809 exportval
= cpystr(simple_addr_string(ta
->addr
, tmp_20k_buf
,
811 if(exportval
&& exportval
[0]){
812 new_current
= new_ltline(old_current
);
813 new_current
->flags
= LT_NONE
;
815 new_current
->exportval
= exportval
;
817 if(ta
->strvalue
&& ta
->strvalue
[0])
818 new_current
->printval
= cpystr(ta
->strvalue
);
820 new_current
->printval
= cpystr(new_current
->exportval
);
825 fs_give((void **)&exportval
);
835 * new_ltline - create new LINES_TO_TAKE, zero it out,
836 * and insert it after current.
837 * NOTE current gets set to the new current.
840 new_ltline(LINES_TO_TAKE
**current
)
844 p
= (LINES_TO_TAKE
*)fs_get(sizeof(LINES_TO_TAKE
));
845 memset((void *)p
, 0, sizeof(LINES_TO_TAKE
));
848 p
->next
= (*current
)->next
;
849 (*current
)->next
= p
;
863 add_addresses_to_talist(struct pine
*ps
, long int msgno
, char *field
,
864 TA_S
**old_current
, struct mail_address
*adrlist
, int checked
)
869 for(addr
= adrlist
; addr
; addr
= addr
->next
){
870 if(addr
->host
&& addr
->host
[0] == '.'){
875 if((h
= pine_fetchheader_lines(ps
->mail_stream
, msgno
,NULL
,fields
)) != NULL
){
881 snprintf(fname
, sizeof(fname
), "%s:", field
);
882 for(p
= h
; (p
= strstr(p
, fname
)) != NULL
; )
883 rplstr(p
, q
-(p
-h
), strlen(fname
), ""); /* strip field strings */
885 sqznewlines(h
); /* blat out CR's & LF's */
886 for(p
= h
; (p
= strchr(p
, TAB
)) != NULL
; )
887 *p
++ = ' '; /* turn TABs to space */
894 new_addr
= (ADDRESS
*)fs_get(sizeof(ADDRESS
));
895 memset(new_addr
, 0, sizeof(ADDRESS
));
898 * Base64 armor plate the gunk to protect against
899 * c-client quoting in output.
901 p
= (char *)rfc822_binary((void *)h
,
902 (unsigned long)strlen(h
), &l
);
905 new_addr
->mailbox
= (char *) fs_get((ll
+1) * sizeof(char));
906 snprintf(new_addr
->mailbox
, ll
+1, "&%s", p
);
907 fs_give((void **)&p
);
908 new_addr
->host
= cpystr(RAWFIELD
);
909 fill_in_ta(old_current
, new_addr
, 0, (char *)NULL
);
910 mail_free_address(&new_addr
);
913 fs_give((void **)&h
);
920 /* no syntax errors in list, add them all */
921 for(addr
= adrlist
; addr
; addr
= addr
->next
){
922 fill_in_ta(old_current
, addr
, checked
, (char *)NULL
);
931 * Look for Text/directory attachments and process them.
932 * Return number of lines added to the ta_list.
935 process_vcard_atts(MAILSTREAM
*stream
, long int msgno
,
936 struct mail_bodystruct
*root
, struct mail_bodystruct
*body
,
937 char *partnum
, TA_S
**ta_list
)
939 char *addrs
, /* comma-separated list of addresses */
966 static char a_comma
= ',';
969 * Look through all the subparts searching for our special type.
971 if(body
&& body
->type
== TYPEMULTIPART
){
972 for(part
= body
->nested
.part
; part
; part
= part
->next
)
973 selected
+= process_vcard_atts(stream
, msgno
, root
, &part
->body
,
976 /* only look in rfc822 subtype of type message */
977 else if(body
&& body
->type
== TYPEMESSAGE
978 && body
->subtype
&& !strucmp(body
->subtype
, "rfc822")){
979 selected
+= process_vcard_atts(stream
, msgno
, root
,
980 body
->nested
.msg
->body
,
983 /* found one (TYPEAPPLICATION for back compat.) */
984 else if(body
&& MIME_VCARD(body
->type
,body
->subtype
)){
986 dprint((4, "\n - found abook attachment to process - \n"));
989 * The Text/directory type has a possible parameter called
990 * "defaulttype" that we need to look for (this is from an old draft
991 * and is supported only for backwards compatibility). There is
992 * also a possible default "charset". (Default charset is also from
993 * an old draft and is no longer allowed.) We don't care about any of
994 * the other parameters.
996 for(parm
= body
->parameter
; parm
; parm
= parm
->next
)
997 if(!strucmp("defaulttype", parm
->attribute
))
1001 defaulttype
= parm
->value
;
1003 /* ...and look for possible charset parameter */
1004 for(parm
= body
->parameter
; parm
; parm
= parm
->next
)
1005 if(!strucmp("charset", parm
->attribute
))
1009 charset
= parm
->value
;
1011 num
= partnum
? cpystr(partnum
) : partno(root
, body
);
1012 lines
= detach_vcard_att(stream
, msgno
, body
, num
);
1014 fs_give((void **)&num
);
1016 nickname
= fullname
= comment
= title
= fcc
= struct_name
= NULL
;
1017 #define CHUNK (size_t)500
1019 /* make comma-separated list of email addresses in addrs */
1020 addrs
= (char *)fs_get((space
+1) * sizeof(char));
1022 for(ll
= lines
; ll
&& *ll
; ll
++){
1027 value
= getaltcharset(*ll
, &tag
, &altcharset
, &is_encoded
);
1031 fs_give((void **)&tag
);
1034 fs_give((void **)&altcharset
);
1040 unsigned long length
;
1042 decoded
= (char *)rfc822_base64((unsigned char *)value
,
1043 (unsigned long)strlen(value
),
1046 decoded
[length
] = '\0';
1050 value
= "<malformed B64 data>";
1053 /* support default tag (back compat.) */
1054 if(*tag
== '\0' && defaulttype
){
1055 fs_give((void **)&tag
);
1056 tag
= cpystr(defaulttype
);
1059 if(!strucmp(tag
, "begin")){ /* vCard BEGIN */
1062 else if(!strucmp(tag
, "end")){ /* vCard END */
1063 if(--vcard_nesting
== 0){
1068 fs_give((void **)&title
);
1071 /* add this entry */
1072 selected
+= vcard_to_ta(addrs
, fullname
, struct_name
,
1073 nickname
, comment
, fcc
, ta_list
);
1078 nickname
= fullname
= comment
= title
= fcc
= NULL
;
1082 /* add another address to addrs */
1083 else if(!strucmp(tag
, "email")){
1085 escval
= vcard_unescape(value
);
1086 encoded
= encode_fullname_of_addrstring(escval
,
1087 (altcharset
&& *altcharset
) ? altcharset
1088 : (charset
&& *charset
)
1090 : ps_global
->posting_charmap
);
1092 /* allocate more space */
1093 if((used
+ strlen(encoded
) + 1) > space
){
1095 fs_resize((void **)&addrs
, (space
+1)*sizeof(char));
1099 strncat(addrs
, ",", space
+1-1-strlen(addrs
));
1100 addrs
[space
-1] = '\0';
1103 strncat(addrs
, encoded
, space
+1-1-strlen(addrs
));
1104 addrs
[space
-1] = '\0';
1105 used
+= (strlen(encoded
) + 1);
1106 fs_give((void **)&encoded
);
1110 else if(!strucmp(tag
, "note") || !strucmp(tag
, "misc")){
1112 escval
= vcard_unescape(value
);
1113 encoded
= rfc1522_encode(tmp_20k_buf
, SIZEOF_20KBUF
,
1114 (unsigned char *)escval
,
1115 (altcharset
&& *altcharset
) ? altcharset
1116 : (charset
&& *charset
)
1118 : ps_global
->posting_charmap
);
1122 fs_give((void **)&comment
);
1124 comment
= cpystr(encoded
);
1128 else if(!strucmp(tag
, "title")){
1130 escval
= vcard_unescape(value
);
1131 encoded
= rfc1522_encode(tmp_20k_buf
, SIZEOF_20KBUF
,
1132 (unsigned char *)escval
,
1133 (altcharset
&& *altcharset
) ? altcharset
1134 : (charset
&& *charset
)
1136 : ps_global
->posting_charmap
);
1140 fs_give((void **)&title
);
1142 title
= cpystr(encoded
);
1146 else if(!strucmp(tag
, "x-fcc")){
1148 escval
= vcard_unescape(value
);
1149 encoded
= rfc1522_encode(tmp_20k_buf
, SIZEOF_20KBUF
,
1150 (unsigned char *)escval
,
1151 (altcharset
&& *altcharset
) ? altcharset
1152 : (charset
&& *charset
)
1154 : ps_global
->posting_charmap
);
1158 fs_give((void **)&fcc
);
1160 fcc
= cpystr(encoded
);
1164 else if(!strucmp(tag
, "fn") || !strucmp(tag
, "cn")){
1166 escval
= vcard_unescape(value
);
1167 encoded
= rfc1522_encode(tmp_20k_buf
, SIZEOF_20KBUF
,
1168 (unsigned char *)escval
,
1169 (altcharset
&& *altcharset
) ? altcharset
1170 : (charset
&& *charset
)
1172 : ps_global
->posting_charmap
);
1176 fs_give((void **)&fullname
);
1178 fullname
= cpystr(encoded
);
1182 else if(!strucmp(tag
, "n")){
1184 * This is a structured name field. It has up to five
1185 * fields separated by ';'. The fields are Family Name (which
1186 * we'll take to be Last name for our purposes), Given Name
1187 * (First name for us), additional names, prefixes, and
1188 * suffixes. We'll ignore prefixes and suffixes.
1190 * If we find one of these records we'll use it in preference
1191 * to the formatted name field (fn).
1193 if(*value
&& *value
!= ';'){
1194 char *last
, *first
, *middle
= NULL
, *rest
= NULL
;
1195 char *esclast
, *escfirst
, *escmiddle
;
1196 static char a_semi
= ';';
1202 while(p
&& (first
= strindex(p
, a_semi
)) != NULL
){
1203 if(first
> last
&& first
[-1] != '\\')
1212 if(first
&& *(first
+1) && *(first
+1) != a_semi
){
1217 while(p
&& (middle
= strindex(p
, a_semi
)) != NULL
){
1218 if(middle
> first
&& middle
[-1] != '\\')
1227 if(middle
&& *(middle
+1) && *(middle
+1) != a_semi
){
1232 while(p
&& (rest
= strindex(p
, a_semi
)) != NULL
){
1233 if(rest
> middle
&& rest
[-1] != '\\')
1239 /* we don't care about the rest */
1250 * Structured name pieces can have multiple values.
1251 * We're just going to take the first value in each part.
1252 * Skip excaped commas, though.
1255 while(p
&& (comma
= strindex(p
, a_comma
)) != NULL
){
1256 if(comma
> last
&& comma
[-1] != '\\'){
1265 while(p
&& (comma
= strindex(p
, a_comma
)) != NULL
){
1266 if(comma
> first
&& comma
[-1] != '\\'){
1275 while(p
&& (comma
= strindex(p
, a_comma
)) != NULL
){
1276 if(comma
> middle
&& comma
[-1] != '\\'){
1284 esclast
= vcard_unescape(last
);
1285 escfirst
= vcard_unescape(first
);
1286 escmiddle
= vcard_unescape(middle
);
1287 snprintf(tmp_20k_buf
+10000, SIZEOF_20KBUF
-10000, "%s%s%s%s%s",
1288 esclast
? esclast
: "",
1289 escfirst
? ", " : "",
1290 escfirst
? escfirst
: "",
1291 escmiddle
? " " : "",
1292 escmiddle
? escmiddle
: "");
1293 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
1295 encoded
= rfc1522_encode(tmp_20k_buf
, SIZEOF_20KBUF
-10000,
1296 (unsigned char *)tmp_20k_buf
+10000,
1297 (altcharset
&& *altcharset
) ? altcharset
1298 : (charset
&& *charset
)
1300 : ps_global
->posting_charmap
);
1301 tmp_20k_buf
[SIZEOF_20KBUF
-10000-1] = '\0';
1303 if(esclast
&& *esclast
&& escfirst
&& *escfirst
){
1305 fs_give((void **)&struct_name
);
1307 struct_name
= cpystr(encoded
);
1310 /* in case we don't get a fullname better than this */
1312 fullname
= cpystr(encoded
);
1316 fs_give((void **)&esclast
);
1318 fs_give((void **)&escfirst
);
1320 fs_give((void **)&escmiddle
);
1323 /* suggested nickname */
1324 else if(!strucmp(tag
, "nickname") || !strucmp(tag
, "x-nickname")){
1328 fs_give((void **)&nickname
);
1331 * Nickname can have multiple values. We're just
1332 * going to take the first. Skip escaped commas, though.
1335 while(p
&& (comma
= strindex(p
, a_comma
)) != NULL
){
1336 if(comma
> value
&& comma
[-1] != '\\'){
1344 nickname
= vcard_unescape(value
);
1349 fs_give((void **)&tag
);
1352 fs_give((void **)&altcharset
);
1355 fs_give((void **)&decoded
);
1358 fs_give((void **)&escval
);
1365 fs_give((void **)&title
);
1368 if(fullname
|| struct_name
|| nickname
|| fcc
1369 || comment
|| (addrs
&& *addrs
))
1370 selected
+= vcard_to_ta(addrs
, fullname
, struct_name
, nickname
,
1371 comment
, fcc
, ta_list
);
1374 fs_give((void **)&addrs
);
1376 free_list_array(&lines
);
1384 cmp_swoop_list(const qsort_t
*a1
, const qsort_t
*a2
)
1386 SWOOP_S
*x
= (SWOOP_S
*)a1
;
1387 SWOOP_S
*y
= (SWOOP_S
*)a2
;
1391 return((x
->dst_enum
> y
->dst_enum
) ? -1
1392 : (x
->dst_enum
== y
->dst_enum
) ? 0 : 1);
1399 return((x
> y
) ? -1 : (x
== y
) ? 0 : 1); /* doesn't matter */
1405 * Take the split out contents of a vCard entry and turn them into a TA.
1407 * Always returns 1, the number of TAs added to ta_list.
1410 vcard_to_ta(char *addrs
, char *fullname
, char *better_fullname
, char *nickname
,
1411 char *comment
, char *fcc
, TA_S
**ta_list
)
1413 ADDRESS
*addrlist
= NULL
;
1416 * Parse it into an addrlist, which is what fill_in_ta wants
1419 rfc822_parse_adrlist(&addrlist
, addrs
, fakedomain
);
1421 addrlist
= mail_newaddr(); /* empty addr, to make right thing happen */
1423 *ta_list
= fill_in_ta(ta_list
, addrlist
, 1,
1425 : better_fullname
? better_fullname
1426 : "Forwarded Entry");
1427 (*ta_list
)->nickname
= nickname
? nickname
: cpystr("");
1428 (*ta_list
)->comment
= comment
;
1429 (*ta_list
)->fcc
= fcc
;
1432 * We are tempted to want to call switch_to_last_comma_first() here when
1433 * we don't have a better_fullname (which is already last, first).
1434 * But, since this is the way it was sent to us we should probably leave
1435 * it alone. That means that if we use a fullname culled from the
1436 * body of a message, or from a header, or from an "N" vcard record,
1437 * we will make it be Last, First; but if we use one from an "FN" record
1440 (*ta_list
)->fullname
= better_fullname
? better_fullname
1443 if((*ta_list
)->nickname
)
1444 convert_possibly_encoded_str_to_utf8(&(*ta_list
)->nickname
);
1446 if((*ta_list
)->comment
)
1447 convert_possibly_encoded_str_to_utf8(&(*ta_list
)->comment
);
1450 convert_possibly_encoded_str_to_utf8(&(*ta_list
)->fcc
);
1452 if((*ta_list
)->fullname
)
1453 convert_possibly_encoded_str_to_utf8(&(*ta_list
)->fullname
);
1456 * The TA list will free its members but we have to take care of this
1457 * extra one that isn't assigned to a TA member.
1459 if(better_fullname
&& fullname
)
1460 fs_give((void **)&fullname
);
1463 mail_free_address(&addrlist
);
1470 * Look through line which is supposed to look like
1472 * type;charset=iso-8859-1;encoding=b: value
1474 * Type might be email, or nickname, ... It is optional because of the
1475 * defaulttype parameter (there isn't such a thing as defaulttype parameters
1476 * as of Nov. 96 draft). The semicolon and colon are special chars. Each
1477 * parameter in this line is a semicolon followed by the parameter type "="
1478 * the parameter value. Parameters are optional, too. There is always a colon,
1479 * followed by value. Whitespace can be everywhere up to where value starts.
1480 * There is also an optional <group> "." preceding the type, which we will
1481 * ignore. It's for grouping related lines.
1482 * (As of July, 97 draft, it is no longer permissible to include a charset
1483 * parameter in each line. Only one is permitted in the content-type mime hdr.)
1484 * (Also as of July, 97 draft, all white space is supposed to be significant.
1485 * We will continue to skip white space surrounding '=' and so forth for
1486 * backwards compatibility and because it does no harm in almost all cases.)
1487 * (As of Nov, 97 draft, some white space is not significant. That is, it is
1488 * allowed in some places.)
1491 * Args: line -- the line to look at
1492 * type -- this is a return value, and is an allocated copy of
1493 * the type, freed by the caller. For example, "email".
1494 * alt -- this is a return value, and is an allocated copy of
1495 * the value of the alternate charset, to be freed by
1496 * the caller. For example, this might be "iso-8859-2".
1497 * encoded -- this is a return value, and is set to 1 if the value
1500 * Return value: a pointer to the start of value, or NULL if the syntax
1501 * isn't right. It's possible for value to be equal to "".
1504 getaltcharset(char *line
, char **type
, char **alt
, int *encoded
)
1506 char *p
, *q
, *left_semi
, *group_dot
, *colon
;
1507 char *start_of_enc
, *start_of_cset
, tmpsave
;
1508 static char *cset
= "charset";
1509 static char *enc
= "encoding";
1520 colon
= strindex(line
, ':');
1524 left_semi
= strindex(line
, ';');
1525 if(left_semi
&& left_semi
> colon
)
1528 group_dot
= strindex(line
, '.');
1529 if(group_dot
&& (group_dot
> colon
|| (left_semi
&& group_dot
> left_semi
)))
1533 * Type is everything up to the semicolon, or the colon if no semicolon.
1534 * However, we want to skip optional <group> ".".
1537 q
= left_semi
? left_semi
: colon
;
1540 *type
= cpystr(group_dot
? group_dot
+1 : line
);
1546 && (p
= srchstr(left_semi
+1, cset
))
1549 p
= skip_white_space(p
);
1551 p
= skip_white_space(p
);
1555 while(p
< colon
&& !isspace((unsigned char)*p
) && *p
!= ';')
1560 *alt
= cpystr(start_of_cset
);
1566 if(encoded
&& left_semi
1567 && (p
= srchstr(left_semi
+1, enc
))
1570 p
= skip_white_space(p
);
1572 p
= skip_white_space(p
);
1576 while(p
< colon
&& !isspace((unsigned char)*p
) && *p
!= ';')
1579 if(*start_of_enc
== 'b' && start_of_enc
+ 1 == p
)
1587 return(skip_white_space(p
));
1592 * Return incoming_fullname in Last, First form.
1593 * The passed in fullname should already be in UTF-8.
1594 * If there is already a comma, we add quotes around the fullname so we can
1595 * tell it isn't a Last, First comma but a real comma in the Fullname.
1597 * Args: incoming_fullname --
1598 * new_full -- passed in pointer to place to put new fullname
1599 * nbuf -- size of new_full
1603 switch_to_last_comma_first(char *incoming_fullname
, char *new_full
, size_t nbuf
)
1606 char *save_end
, *nf
, *inc
, *p
= NULL
;
1611 if(incoming_fullname
== NULL
){
1616 /* get rid of leading white space */
1617 for(inc
= incoming_fullname
; *inc
&& isspace((unsigned char)*inc
); inc
++)
1620 /* get rid of trailing white space */
1621 for(p
= inc
+ strlen(inc
) - 1; *p
&& p
>= inc
&&
1622 isspace((unsigned char)*p
); p
--)
1626 if(save_end
== inc
){
1631 save_value
= *save_end
; /* so we don't alter incoming_fullname */
1634 memset(new_full
, 0, nbuf
); /* need this the way it is done below */
1636 if(strindex(inc
, ',') != NULL
){
1640 * Quote already existing comma.
1642 * We'll get this wrong if it is already quoted but the quote
1643 * doesn't start right at the beginning.
1647 if(nf
-new_full
< nbuf
-1)
1651 strncpy(nf
, inc
, MIN(nbuf
- (add_quotes
? 3 : 1), nbuf
-(nf
-new_full
)-1));
1652 new_full
[nbuf
-1] = '\0';
1654 strncat(nf
, "\"", nbuf
-(nf
-new_full
)-1);
1655 new_full
[nbuf
-1] = '\0';
1658 else if(strindex(inc
, SPACE
)){
1661 end
= new_full
+ nbuf
- 1;
1664 * Switch First Middle Last to Last, First Middle
1667 /* last points to last word, which does exist */
1668 last
= skip_white_space(strrindex(inc
, SPACE
));
1671 for(p
= last
; *p
&& nf
< end
-2 && nf
-new_full
< nbuf
; *nf
++ = *p
++)
1674 if(nf
-new_full
< nbuf
-1)
1677 if(nf
-new_full
< nbuf
-1)
1680 /* copy First Middle */
1681 for(p
= inc
; p
< last
&& nf
< end
&& nf
-new_full
< nbuf
-1; *nf
++ = *p
++)
1684 new_full
[nbuf
-1] = '\0';
1686 removing_trailing_white_space(new_full
);
1689 strncpy(new_full
, inc
, nbuf
-1);
1691 new_full
[nbuf
-1] = '\0';
1693 *save_end
= save_value
;
1698 * Fetch this body part, content-decode it if necessary, split it into
1699 * lines, and return the lines in an allocated array, which is freed
1700 * by the caller. Folded lines are replaced by single long lines.
1703 detach_vcard_att(MAILSTREAM
*stream
, long int msgno
, struct mail_bodystruct
*body
, char *partnum
)
1705 unsigned long length
;
1706 char *text
= NULL
, /* raw text */
1707 *dtext
= NULL
, /* content-decoded text */
1708 **res
= NULL
, /* array of decoded lines */
1709 *lptr
, /* line pointer */
1713 /* make our own copy of text so we can change it */
1714 text
= cpystr(mail_fetchbody_full(stream
, msgno
, partnum
, &length
,FT_PEEK
));
1715 text
[length
] = '\0';
1717 /* decode the text */
1718 switch(body
->encoding
){
1727 dtext
= (char *)rfc822_base64((unsigned char *)text
,
1728 (unsigned long)strlen(text
),
1731 dtext
[length
] = '\0';
1733 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
1734 "Malformed B64 data in address book attachment.");
1737 fs_give((void **)&text
);
1741 case ENCQUOTEDPRINTABLE
:
1742 dtext
= (char *)rfc822_qprint((unsigned char *)text
,
1743 (unsigned long)strlen(text
),
1746 dtext
[length
] = '\0';
1748 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
1749 "Malformed QP data in address book attachment.");
1752 fs_give((void **)&text
);
1757 /* count the lines */
1760 for(lptr
= next_line
; lptr
&& *lptr
; lptr
= next_line
){
1761 for(next_line
= lptr
; *next_line
; next_line
++){
1763 * Find end of current line.
1765 if(*next_line
== '\r' && *(next_line
+1) == '\n'){
1767 /* not a folded line, count it */
1768 if(!*next_line
|| (*next_line
!= SPACE
&& *next_line
!= TAB
))
1776 /* allocate space for resulting array of lines */
1777 res
= (char **)fs_get((count
+ 1) * sizeof(char *));
1778 memset((void *)res
, 0, (count
+ 1) * sizeof(char *));
1780 for(i
=0, lptr
=next_line
; lptr
&& *lptr
&& i
< count
; lptr
=next_line
, i
++){
1782 * Move next_line to start of next line and null terminate
1785 for(next_line
= lptr
; *next_line
; next_line
++){
1787 * Find end of current line.
1789 if(*next_line
== '\r' && *(next_line
+1) == '\n'){
1791 /* not a folded line, terminate it */
1792 if(!*next_line
|| (*next_line
!= SPACE
&& *next_line
!= TAB
)){
1793 *(next_line
-2) = '\0';
1799 /* turn folded lines into long lines in place */
1801 res
[i
] = cpystr(lptr
);
1805 fs_give((void **)&dtext
);
1813 * Look for possible addresses in the first text part of a message for
1814 * use by TakeAddr command.
1815 * Returns the number of TA lines added.
1818 grab_addrs_from_body(MAILSTREAM
*stream
, long int msgno
,
1819 struct mail_bodystruct
*body
, TA_S
**ta_list
)
1821 #define MAXLINESZ 2000
1822 char line
[MAXLINESZ
+ 1];
1825 SourceType src
= CharStar
;
1826 int added
= 0, failure
;
1828 ATTACH_S
*a
= ps_global
->atmts
;
1830 dprint((9, "\n - grab_addrs_from_body - \n"));
1833 * If it is text/plain or it is multipart with a first part of text/plain,
1834 * we want to continue, else forget it.
1836 if(!((body
->type
== TYPETEXT
&& body
->subtype
1837 && ALLOWED_SUBTYPE(body
->subtype
))
1839 (body
->type
== TYPEMULTIPART
&& body
->nested
.part
1840 && body
->nested
.part
->body
.type
== TYPETEXT
1841 && ALLOWED_SUBTYPE(body
->nested
.part
->body
.subtype
))))
1848 if((so
= so_get(src
, NULL
, EDIT_ACCESS
)) == NULL
)
1851 gf_set_so_writec(&pc
, so
);
1853 if(body
->type
== TYPEMULTIPART
1854 && strucmp(body
->subtype
, "ALTERNATIVE") == 0
1855 && a
&& a
+1 && (a
+1)->shown
)
1858 failure
= !get_body_part_text(stream
, body
, msgno
, partno
, 0L, pc
,
1859 NULL
, NULL
, GBPT_NONE
);
1861 gf_clear_so_writec(so
);
1870 while(get_line_of_message(so
, line
, sizeof(line
))){
1872 char save_end
, *start
, *tmp_a_string
, *tmp_personal
, *p
;
1875 /* process each @ in the line */
1876 for(p
= (char *) line
; (start
= mail_addr_scan(p
, &n
)); p
= start
+ n
){
1878 tmp_personal
= NULL
;
1880 if(start
> line
&& *(start
-1) == '<' && *(start
+n
) == '>'){
1881 int words
, in_quote
;
1885 * Take a shot at looking for full name
1886 * If we can find a colon maybe we've got a header line
1887 * embedded in the body.
1888 * This is real ad hoc.
1892 * Go back until we run into a character that probably
1893 * isn't a fullname character.
1896 in_quote
= words
= 0;
1897 while(fn_start
> p
&& (in_quote
|| !(*(fn_start
-1) == ':'
1898 || *(fn_start
-1) == ';' || *(fn_start
-1) == ','))){
1900 if(!in_quote
&& isspace((unsigned char)*fn_start
))
1902 else if(*fn_start
== '"' &&
1903 (fn_start
== p
|| *(fn_start
-1) != '\\')){
1916 /* wasn't a real quote, forget about fullname */
1920 /* Skip forward over the white space. */
1921 while(isspace((unsigned char)*fn_start
) || *fn_start
== '(')
1925 * Make sure the first word is capitalized.
1926 * (just so it is more likely it is a name)
1928 while(fn_start
< start
-1 &&
1929 !(isupper(*fn_start
) || *fn_start
== '"')){
1930 if(*fn_start
== '('){
1935 /* skip forward over this word */
1936 while(fn_start
< start
-1 &&
1937 !isspace((unsigned char)*fn_start
))
1940 while(fn_start
< start
-1 &&
1941 isspace((unsigned char)*fn_start
))
1945 if(fn_start
< start
-1){
1946 char *fn_end
, save_fn_end
;
1948 /* remove white space between fullname and start */
1950 while(fn_end
> fn_start
1951 && isspace((unsigned char)*(fn_end
- 1)))
1954 save_fn_end
= *fn_end
;
1957 /* remove matching quotes */
1958 if(*fn_start
== '"' && *(fn_end
-1) == '"'){
1960 *fn_end
= save_fn_end
;
1962 save_fn_end
= *fn_end
;
1968 tmp_personal
= cpystr(fn_start
);
1970 *fn_end
= save_fn_end
;
1975 save_end
= *(start
+n
);
1977 /* rfc822_parse_adrlist feels free to destroy input so send copy */
1978 tmp_a_string
= cpystr(start
);
1979 *(start
+n
) = save_end
;
1981 ps_global
->c_client_error
[0] = '\0';
1982 rfc822_parse_adrlist(&addr
, tmp_a_string
, fakedomain
);
1984 fs_give((void **)&tmp_a_string
);
1989 fs_give((void **)&addr
->personal
);
1991 addr
->personal
= tmp_personal
;
1994 fs_give((void **)&tmp_personal
);
1997 if((addr
&& addr
->host
&& addr
->host
[0] == '@') ||
1998 ps_global
->c_client_error
[0]){
1999 mail_free_address(&addr
);
2003 if(addr
&& addr
->mailbox
&& addr
->host
){
2005 *ta_list
= fill_in_ta(ta_list
, addr
, 0, (char *)NULL
);
2009 mail_free_address(&addr
);
2019 * Get the next line of the object pointed to by source.
2020 * Skips empty lines.
2021 * Linebuf is a passed in buffer to put the line in.
2022 * Linebuf is null terminated and \r and \n chars are removed.
2023 * 0 is returned when there is no more input.
2026 get_line_of_message(STORE_S
*source
, char *linebuf
, int linebuflen
)
2034 while(so_readc(&c
, source
)){
2035 if(c
== '\n' || c
== '\r'){
2041 if(pos
>= linebuflen
- 2)
2046 linebuf
[pos
] = '\0';
2053 * Inserts a new entry based on addr in the TA list.
2054 * Makes sure everything is UTF-8. That is,
2055 * Values used for strvalue will be converted to UTF-8 first.
2056 * Personal name fields of addr args will be converted to UTF-8.
2058 * Args: old_current -- the TA list
2059 * addr -- the address for this line
2060 * checked -- start this line out checked
2061 * print_string -- if non-null, this line is informational and is just
2062 * printed out, it can't be selected
2065 fill_in_ta(TA_S
**old_current
, struct mail_address
*addr
, int checked
, char *print_string
)
2069 /* c-client convention for group syntax, which we don't want to deal with */
2070 if(!print_string
&& (!addr
|| !addr
->mailbox
|| !addr
->host
))
2071 new_current
= *old_current
;
2074 new_current
= new_taline(old_current
);
2075 if(print_string
&& addr
){ /* implied List when here */
2076 new_current
->frwrded
= 1;
2077 new_current
->skip_it
= 0;
2078 new_current
->print
= 0;
2079 new_current
->checked
= checked
!= 0;
2080 new_current
->addr
= copyaddrlist(addr
);
2081 decode_addr_names_to_utf8(new_current
->addr
);
2082 new_current
->strvalue
= cpystr(print_string
);
2083 convert_possibly_encoded_str_to_utf8(&new_current
->strvalue
);
2085 else if(print_string
){
2086 new_current
->frwrded
= 0;
2087 new_current
->skip_it
= 1;
2088 new_current
->print
= 1;
2089 new_current
->checked
= 0;
2090 new_current
->addr
= 0;
2091 new_current
->strvalue
= cpystr(print_string
);
2092 convert_possibly_encoded_str_to_utf8(&new_current
->strvalue
);
2095 new_current
->frwrded
= 0;
2096 new_current
->skip_it
= 0;
2097 new_current
->print
= 0;
2098 new_current
->checked
= checked
!= 0;
2099 new_current
->addr
= copyaddr(addr
);
2100 decode_addr_names_to_utf8(new_current
->addr
);
2101 if(addr
->host
[0] == '.')
2102 new_current
->strvalue
= cpystr("Error in address (ok to try Take anyway)");
2107 len
= est_size(new_current
->addr
);
2108 bufp
= (char *) fs_get(len
* sizeof(char));
2109 new_current
->strvalue
= cpystr(addr_string(new_current
->addr
, bufp
, len
));
2110 fs_give((void **) &bufp
);
2113 convert_possibly_encoded_str_to_utf8(&new_current
->strvalue
);
2117 return(new_current
);
2122 eliminate_dups_and_us(TA_S
*list
)
2124 return(eliminate_dups_and_maybe_us(list
, 1));
2129 eliminate_dups_but_not_us(TA_S
*list
)
2131 return(eliminate_dups_and_maybe_us(list
, 0));
2136 * Look for dups in list and mark them so we'll skip them.
2138 * We also throw out any addresses that are us (if us_too is set), since
2139 * we won't usually want to take ourselves to the addrbook.
2140 * On the otherhand, if there is nothing but us, we leave it.
2142 * Don't toss out forwarded entries.
2144 * Returns the number of dups eliminated that were also selected.
2147 eliminate_dups_and_maybe_us(TA_S
*list
, int us_too
)
2154 for(ta
= list
; ta
; ta
= ta
->next
){
2156 if(ta
->skip_it
|| ta
->frwrded
) /* already tossed or forwarded */
2161 /* Check addr "a" versus tail of the list */
2162 for(tb
= ta
->next
; tb
; tb
= tb
->next
){
2163 if(tb
->skip_it
|| tb
->frwrded
) /* already tossed or forwarded */
2167 if(dup_addrs(a
, b
)){
2168 if(ta
->checked
|| !(tb
->checked
)){
2175 else{ /* tb->checked */
2185 /* check whether all remaining addrs are us */
2186 for(ta
= list
; ta
; ta
= ta
->next
){
2188 if(ta
->skip_it
) /* already tossed */
2191 if(ta
->frwrded
) /* forwarded entry, so not us */
2196 if(!address_is_us(a
, ps_global
))
2201 * if at least one address that isn't us, remove all of us from
2205 for(ta
= list
; ta
; ta
= ta
->next
){
2207 if(ta
->skip_it
|| ta
->frwrded
) /* already tossed or forwarded */
2212 if(address_is_us(a
, ps_global
)){
2228 * Returns 1 if x and y match, 0 if not.
2231 dup_addrs(struct mail_address
*x
, struct mail_address
*y
)
2233 return(x
&& y
&& strucmp(x
->mailbox
, y
->mailbox
) == 0
2234 && strucmp(x
->host
, y
->host
) == 0);