2 * ========================================================================
3 * Copyright 2006-2007 University of Washington
4 * Copyright 2013-2022 Eduardo Chappa
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * ========================================================================
15 /*======================================================================
17 Mostly support for Take Address command.
21 #include "../pith/headers.h"
22 #include "../pith/takeaddr.h"
23 #include "../pith/conf.h"
24 #include "../pith/bldaddr.h"
25 #include "../pith/adrbklib.h"
26 #include "../pith/copyaddr.h"
27 #include "../pith/addrstring.h"
28 #include "../pith/status.h"
29 #include "../pith/mailview.h"
30 #include "../pith/reply.h"
31 #include "../pith/url.h"
32 #include "../pith/mailpart.h"
33 #include "../pith/sequence.h"
34 #include "../pith/stream.h"
35 #include "../pith/busy.h"
36 #include "../pith/ablookup.h"
37 #include "../pith/list.h"
40 static char *fakedomain
= "@";
41 long msgno_for_pico_callback
;
42 BODY
*body_for_pico_callback
= NULL
;
43 ENVELOPE
*env_for_pico_callback
= NULL
;
47 * Previous selectable TA line.
48 * skips over the elements with skip_it set
51 pre_sel_taline(TA_S
*current
)
59 while(p
&& p
->skip_it
)
67 * Previous TA line, selectable or just printable.
68 * skips over the elements with skip_it set
71 pre_taline(TA_S
*current
)
79 while(p
&& (p
->skip_it
&& !p
->print
))
87 * Next selectable TA line.
88 * skips over the elements with skip_it set
91 next_sel_taline(TA_S
*current
)
99 while(p
&& p
->skip_it
)
107 * Next TA line, including print only lines.
108 * skips over the elements with skip_it set unless they are print lines
111 next_taline(TA_S
*current
)
119 while(p
&& (p
->skip_it
&& !p
->print
))
127 * Mark all of the addresses with a check.
129 * Args: f_line -- the first ta line
131 * Returns the number of lines checked.
134 ta_mark_all(TA_S
*f_line
)
137 int how_many_selected
= 0;
139 for(ctmp
= f_line
; ctmp
; ctmp
= next_sel_taline(ctmp
)){
144 return(how_many_selected
);
149 * Does the takeaddr list consist of a single address?
151 * Args: f_line -- the first ta line
153 * Returns 1 if only one address and 0 otherwise.
156 is_talist_of_one(TA_S
*f_line
)
161 /* there is at least one, see if there are two */
162 if(next_sel_taline(f_line
) != NULL
)
170 * Turn off all of the check marks.
172 * Args: f_line -- the first ta line
174 * Returns the number of lines checked (0).
177 ta_unmark_all(TA_S
*f_line
)
181 for(ctmp
= f_line
; ctmp
; ctmp
= ctmp
->next
)
189 * new_taline - create new TA_S, zero it out, and insert it after current.
190 * NOTE current gets set to the new TA_S, too!
193 new_taline(TA_S
**current
)
197 p
= (TA_S
*)fs_get(sizeof(TA_S
));
198 memset((void *)p
, 0, sizeof(TA_S
));
201 p
->next
= (*current
)->next
;
202 (*current
)->next
= p
;
216 free_taline(TA_S
**p
)
220 mail_free_address(&(*p
)->addr
);
223 fs_give((void **)&(*p
)->strvalue
);
226 fs_give((void **)&(*p
)->nickname
);
229 fs_give((void **)&(*p
)->fullname
);
232 fs_give((void **)&(*p
)->fcc
);
235 fs_give((void **)&(*p
)->comment
);
238 (*p
)->prev
->next
= (*p
)->next
;
241 (*p
)->next
->prev
= (*p
)->prev
;
249 free_talines(TA_S
**ta_list
)
251 TA_S
*current
, *ctmp
;
253 if(ta_list
&& (current
= (*ta_list
))){
255 current
= current
->prev
;
258 ctmp
= current
->next
;
259 free_taline(¤t
);
269 free_ltline(LINES_TO_TAKE
**p
)
273 fs_give((void **)&(*p
)->printval
);
276 fs_give((void **)&(*p
)->exportval
);
279 (*p
)->prev
->next
= (*p
)->next
;
282 (*p
)->next
->prev
= (*p
)->prev
;
290 free_ltlines(LINES_TO_TAKE
**lt_list
)
292 LINES_TO_TAKE
*current
, *ctmp
;
294 if(lt_list
&& (current
= (*lt_list
))){
296 current
= current
->prev
;
299 ctmp
= current
->next
;
300 free_ltline(¤t
);
310 * Return the first selectable TakeAddr line.
312 * Args: q -- any line in the list
315 first_sel_taline(TA_S
*q
)
323 /* back up to the head of the list */
326 q
= pre_sel_taline(q
);
330 * If first->skip_it, that means we were already past the first
331 * legitimate line, so we have to look in the other direction.
334 first
= next_sel_taline(first
);
341 * Return the last selectable TakeAddr line.
343 * Args: q -- any line in the list
346 last_sel_taline(TA_S
*q
)
354 /* go to the end of the list */
357 q
= next_sel_taline(q
);
361 * If last->skip_it, that means we were already past the last
362 * legitimate line, so we have to look in the other direction.
365 last
= pre_sel_taline(last
);
372 * Return the first TakeAddr line, selectable or just printable.
374 * Args: q -- any line in the list
377 first_taline(TA_S
*q
)
385 /* back up to the head of the list */
392 * If first->skip_it, that means we were already past the first
393 * legitimate line, so we have to look in the other direction.
395 if(first
->skip_it
&& !first
->print
)
396 first
= next_taline(first
);
403 * Find the first TakeAddr line which is checked, beginning with the
406 * Args: head -- A passed in TakeAddr line, usually will be the first
408 * Result: returns a pointer to the first checked line.
409 * NULL if no such line
412 first_checked(TA_S
*head
)
418 for(p
= head
; p
; p
= next_sel_taline(p
))
419 if(p
->checked
&& !p
->skip_it
)
427 * Form a list of strings which are addresses to go in a list.
428 * These are entries in a list, so can be full rfc822 addresses.
429 * The strings are allocated here.
431 * Args: head -- A passed in TakeAddr line, usually will be the first
433 * Result: returns an allocated array of pointers to allocated strings
436 list_of_checked(TA_S
*head
)
443 /* first count them */
444 for(p
= head
, count
= 0; p
; p
= next_sel_taline(p
)){
445 if(p
->checked
&& !p
->skip_it
){
448 * Remove fullname, fcc, comment, and nickname since not
449 * appropriate for list values.
452 fs_give((void **)&p
->fullname
);
454 fs_give((void **)&p
->fcc
);
456 fs_give((void **)&p
->comment
);
458 fs_give((void **)&p
->nickname
);
460 for(a
= p
->addr
; a
; a
= a
->next
)
465 * Don't even attempt to include bogus addresses in
466 * the list. If the user wants to get at those, they
467 * have to try taking only that single address.
469 if(p
->addr
&& p
->addr
->host
&& p
->addr
->host
[0] == '.')
477 /* allocate pointers */
478 list
= (char **)fs_get((count
+ 1) * sizeof(char *));
479 memset((void *)list
, 0, (count
+ 1) * sizeof(char *));
483 /* allocate and point to address strings */
484 for(p
= head
; p
; p
= next_sel_taline(p
)){
485 if(p
->checked
&& !p
->skip_it
){
487 for(a
= p
->addr
; a
; a
= a
->next
){
495 bufp
= (char *) fs_get(len
* sizeof(char));
496 *cur
++ = cpystr(addr_string(a
, bufp
, len
));
498 fs_give((void **)&bufp
);
501 *cur
++ = cpystr(p
->strvalue
);
508 /* jpf: This used to be the guts of cmd_take_addr, but I made this
509 * function so I could generalize with WP. It still has some display
510 * stuff that we need make sure not to do when in WP mode.
512 * Set things up for when we take addresses
514 * Args: ps -- pine state
515 * msgmap -- the MessageMap
516 * ta_ret -- the take addr lines
517 * selected_num -- the number of selectable addresses
518 * flags -- takeaddr flags, like whether or not
519 * we're doing aggs, and whether to do prompts
521 * Returns: -1 on "failure"
524 set_up_takeaddr(int cmd
, struct pine
*ps
, MSGNO_S
*msgmap
, TA_S
**ta_ret
,
525 int *selected_num
, int flags
, int (*att_addr_f
)(TA_S
*, int))
528 ENVELOPE
*env
= NULL
;
529 int how_many_selected
= 0,
532 special_processing
= 0,
534 TA_S
*current
= NULL
,
538 *special_body
= NULL
,
541 dprint((2, "\n - taking address into address book - \n"));
543 if(!(cmd
== 'a' || cmd
== 'e'))
546 if((flags
& TA_AGG
) && !pseudo_selected(ps_global
->mail_stream
, msgmap
))
549 if(mn_get_total(msgmap
) > 0 && mn_total_cur(msgmap
) == 1)
550 special_processing
= 1;
555 /* this is a non-selectable label */
556 current
= fill_in_ta(¤t
, (ADDRESS
*) NULL
, 0,
557 /* TRANSLATORS: This is a heading describing some addresses the
558 user will have the chance to choose from. */
559 _(" These entries are taken from the attachments "));
560 prev_comment_line
= current
;
563 * Add addresses from special attachments for each message.
566 for(i
= mn_first_cur(msgmap
); i
> 0L; i
= mn_next_cur(msgmap
)){
567 env
= pine_mail_fetchstructure(ps
->mail_stream
, mn_m2raw(msgmap
, i
), &body
);
569 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
570 _("Can't take address into address book. Error accessing folder"));
575 added
+= process_vcard_atts(ps
->mail_stream
, mn_m2raw(msgmap
, i
),
576 body
, body
, NULL
, ¤t
);
582 && (*att_addr_f
)(current
, added
) <= 0)
585 if(!(flags
& TA_NOPROMPT
))
586 we_cancel
= busy_cue(NULL
, NULL
, 1);
589 * add a comment line to separate attachment address lines
590 * from header address lines
593 current
= fill_in_ta(¤t
, (ADDRESS
*) NULL
, 0,
594 /* TRANSLATORS: msg is message */
595 _(" These entries are taken from the msg headers "));
596 prev_comment_line
= current
;
597 how_many_selected
+= added
;
600 else{ /* turn off header comment, and no separator comment */
601 prev_comment_line
->print
= 0;
602 prev_comment_line
= NULL
;
606 * Add addresses from headers of messages.
608 for(i
= mn_first_cur(msgmap
); i
> 0L; i
= mn_next_cur(msgmap
)){
610 if(special_processing
)
611 body_h
= &special_body
;
615 env
= pine_mail_fetchstructure(ps
->mail_stream
, mn_m2raw(msgmap
, i
),
621 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
622 _("Can't take address into address book. Error accessing folder"));
627 added
= add_addresses_to_talist(ps
, i
, "from", ¤t
,
628 env
->from
, select_froms
);
630 how_many_selected
+= added
;
632 if(!address_is_same(env
->from
, env
->reply_to
))
633 (void)add_addresses_to_talist(ps
, i
, "reply-to", ¤t
,
636 if(!address_is_same(env
->from
, env
->sender
))
637 (void)add_addresses_to_talist(ps
, i
, "sender", ¤t
,
640 (void)add_addresses_to_talist(ps
, i
, "to", ¤t
, env
->to
, 0);
641 (void)add_addresses_to_talist(ps
, i
, "cc", ¤t
, env
->cc
, 0);
642 (void)add_addresses_to_talist(ps
, i
, "bcc", ¤t
, env
->bcc
, 0);
646 * Check to see if we added an explanatory line about the
647 * header addresses but no header addresses. If so, remove the
650 if(prev_comment_line
){
652 eliminate_dups_and_us(first_sel_taline(current
));
653 for(ta
= prev_comment_line
->next
; ta
; ta
= ta
->next
)
657 /* all entries were skip_it entries, turn off print */
659 prev_comment_line
->print
= 0;
660 prev_comment_line
= NULL
;
665 * If there's only one message we let the user view it using ^T
666 * from the header editor.
668 if(!(flags
& TA_NOPROMPT
) && special_processing
&& env
&& special_body
){
669 msgno_for_pico_callback
= mn_m2raw(msgmap
, mn_first_cur(msgmap
));
670 body_for_pico_callback
= special_body
;
671 env_for_pico_callback
= env
;
674 env_for_pico_callback
= NULL
;
675 body_for_pico_callback
= NULL
;
678 current
= fill_in_ta(¤t
, (ADDRESS
*)NULL
, 0,
679 _(" Below this line are some possibilities taken from the text of the msg "));
680 prev_comment_line
= current
;
683 * Add addresses from the text of the body.
686 for(i
= mn_first_cur(msgmap
); i
> 0L; i
= mn_next_cur(msgmap
)){
689 env
= pine_mail_fetchstructure(ps
->mail_stream
, mn_m2raw(msgmap
, i
),
692 added
+= grab_addrs_from_body(ps
->mail_stream
,
698 prev_comment_line
->print
= 0;
699 prev_comment_line
= NULL
;
703 * Check to see if all we added are dups.
704 * If so, remove the comment line.
706 if(prev_comment_line
){
707 how_many_selected
-= eliminate_dups_and_us(first_sel_taline(current
));
708 for(ta
= prev_comment_line
->next
; ta
; ta
= ta
->next
)
712 /* all entries were skip_it entries, turn off print */
714 prev_comment_line
->print
= 0;
721 env_for_pico_callback
= NULL
;
722 body_for_pico_callback
= NULL
;
724 ps
->mangled_screen
= 1;
727 restore_selected(msgmap
);
732 *selected_num
= how_many_selected
;
739 convert_ta_to_lines(TA_S
*ta_list
, LINES_TO_TAKE
**old_current
)
743 LINES_TO_TAKE
*new_current
;
744 char *exportval
, *printval
;
747 for(ta
= first_sel_taline(ta_list
);
749 ta
= next_sel_taline(ta
)){
755 fs_give((void **)&ta
->fullname
);
757 fs_give((void **)&ta
->fcc
);
759 fs_give((void **)&ta
->comment
);
761 fs_give((void **)&ta
->nickname
);
763 else if(ta
->addr
&& ta
->addr
->host
&& ta
->addr
->host
[0] == '.')
767 for(a
= ta
->addr
; a
; a
= a
->next
){
770 if(a
->host
&& a
->host
[0] == '.')
776 exportval
= cpystr(simple_addr_string(a
, tmp_20k_buf
,
778 if(!exportval
|| !exportval
[0]){
780 fs_give((void **)&exportval
);
786 printval
= addr_list_string(a
, NULL
, 0);
787 if(!printval
|| !printval
[0]){
789 fs_give((void **)&printval
);
791 printval
= cpystr(exportval
);
794 new_current
= new_ltline(old_current
);
795 new_current
->flags
= LT_NONE
;
797 new_current
->printval
= printval
;
798 new_current
->exportval
= exportval
;
805 exportval
= cpystr(simple_addr_string(ta
->addr
, tmp_20k_buf
,
807 if(exportval
&& exportval
[0]){
808 new_current
= new_ltline(old_current
);
809 new_current
->flags
= LT_NONE
;
811 new_current
->exportval
= exportval
;
813 if(ta
->strvalue
&& ta
->strvalue
[0])
814 new_current
->printval
= cpystr(ta
->strvalue
);
816 new_current
->printval
= cpystr(new_current
->exportval
);
821 fs_give((void **)&exportval
);
831 * new_ltline - create new LINES_TO_TAKE, zero it out,
832 * and insert it after current.
833 * NOTE current gets set to the new current.
836 new_ltline(LINES_TO_TAKE
**current
)
840 p
= (LINES_TO_TAKE
*)fs_get(sizeof(LINES_TO_TAKE
));
841 memset((void *)p
, 0, sizeof(LINES_TO_TAKE
));
844 p
->next
= (*current
)->next
;
845 (*current
)->next
= p
;
859 add_addresses_to_talist(struct pine
*ps
, long int msgno
, char *field
,
860 TA_S
**old_current
, struct mail_address
*adrlist
, int checked
)
865 for(addr
= adrlist
; addr
; addr
= addr
->next
){
866 if(addr
->host
&& addr
->host
[0] == '.'){
871 if((h
= pine_fetchheader_lines(ps
->mail_stream
, msgno
,NULL
,fields
)) != NULL
){
877 snprintf(fname
, sizeof(fname
), "%s:", field
);
878 for(p
= h
; (p
= strstr(p
, fname
)) != NULL
; )
879 rplstr(p
, q
-(p
-h
), strlen(fname
), ""); /* strip field strings */
881 sqznewlines(h
); /* blat out CR's & LF's */
882 for(p
= h
; (p
= strchr(p
, TAB
)) != NULL
; )
883 *p
++ = ' '; /* turn TABs to space */
890 new_addr
= (ADDRESS
*)fs_get(sizeof(ADDRESS
));
891 memset(new_addr
, 0, sizeof(ADDRESS
));
894 * Base64 armor plate the gunk to protect against
895 * c-client quoting in output.
897 p
= (char *)rfc822_binary((void *)h
,
898 (unsigned long)strlen(h
), &l
);
901 new_addr
->mailbox
= (char *) fs_get((ll
+1) * sizeof(char));
902 snprintf(new_addr
->mailbox
, ll
+1, "&%s", p
);
903 fs_give((void **)&p
);
904 new_addr
->host
= cpystr(RAWFIELD
);
905 fill_in_ta(old_current
, new_addr
, 0, (char *)NULL
);
906 mail_free_address(&new_addr
);
909 fs_give((void **)&h
);
916 /* no syntax errors in list, add them all */
917 for(addr
= adrlist
; addr
; addr
= addr
->next
){
918 fill_in_ta(old_current
, addr
, checked
, (char *)NULL
);
927 * Look for Text/directory attachments and process them.
928 * Return number of lines added to the ta_list.
931 process_vcard_atts(MAILSTREAM
*stream
, long int msgno
,
932 struct mail_bodystruct
*root
, struct mail_bodystruct
*body
,
933 char *partnum
, TA_S
**ta_list
)
935 char *addrs
, /* comma-separated list of addresses */
962 static char a_comma
= ',';
965 * Look through all the subparts searching for our special type.
967 if(body
&& body
->type
== TYPEMULTIPART
){
968 for(part
= body
->nested
.part
; part
; part
= part
->next
)
969 selected
+= process_vcard_atts(stream
, msgno
, root
, &part
->body
,
972 /* only look in rfc822 subtype of type message */
973 else if(body
&& body
->type
== TYPEMESSAGE
974 && body
->subtype
&& !strucmp(body
->subtype
, "rfc822")){
975 selected
+= process_vcard_atts(stream
, msgno
, root
,
976 body
->nested
.msg
->body
,
979 /* found one (TYPEAPPLICATION for back compat.) */
980 else if(body
&& MIME_VCARD(body
->type
,body
->subtype
)){
982 dprint((4, "\n - found abook attachment to process - \n"));
985 * The Text/directory type has a possible parameter called
986 * "defaulttype" that we need to look for (this is from an old draft
987 * and is supported only for backwards compatibility). There is
988 * also a possible default "charset". (Default charset is also from
989 * an old draft and is no longer allowed.) We don't care about any of
990 * the other parameters.
992 for(parm
= body
->parameter
; parm
; parm
= parm
->next
)
993 if(!strucmp("defaulttype", parm
->attribute
))
997 defaulttype
= parm
->value
;
999 /* ...and look for possible charset parameter */
1000 for(parm
= body
->parameter
; parm
; parm
= parm
->next
)
1001 if(!strucmp("charset", parm
->attribute
))
1005 charset
= parm
->value
;
1007 num
= partnum
? cpystr(partnum
) : partno(root
, body
);
1008 lines
= detach_vcard_att(stream
, msgno
, body
, num
);
1010 fs_give((void **)&num
);
1012 nickname
= fullname
= comment
= title
= fcc
= struct_name
= NULL
;
1013 #define CHUNK (size_t)500
1015 /* make comma-separated list of email addresses in addrs */
1016 addrs
= (char *)fs_get((space
+1) * sizeof(char));
1018 for(ll
= lines
; ll
&& *ll
; ll
++){
1023 value
= getaltcharset(*ll
, &tag
, &altcharset
, &is_encoded
);
1027 fs_give((void **)&tag
);
1030 fs_give((void **)&altcharset
);
1036 unsigned long length
;
1038 decoded
= (char *)rfc822_base64((unsigned char *)value
,
1039 (unsigned long)strlen(value
),
1042 decoded
[length
] = '\0';
1046 value
= "<malformed B64 data>";
1049 /* support default tag (back compat.) */
1050 if(*tag
== '\0' && defaulttype
){
1051 fs_give((void **)&tag
);
1052 tag
= cpystr(defaulttype
);
1055 if(!strucmp(tag
, "begin")){ /* vCard BEGIN */
1058 else if(!strucmp(tag
, "end")){ /* vCard END */
1059 if(--vcard_nesting
== 0){
1064 fs_give((void **)&title
);
1067 /* add this entry */
1068 selected
+= vcard_to_ta(addrs
, fullname
, struct_name
,
1069 nickname
, comment
, fcc
, ta_list
);
1074 nickname
= fullname
= comment
= title
= fcc
= NULL
;
1078 /* add another address to addrs */
1079 else if(!strucmp(tag
, "email")){
1081 escval
= vcard_unescape(value
);
1082 encoded
= encode_fullname_of_addrstring(escval
,
1083 (altcharset
&& *altcharset
) ? altcharset
1084 : (charset
&& *charset
)
1086 : ps_global
->posting_charmap
);
1088 /* allocate more space */
1089 if((used
+ strlen(encoded
) + 1) > space
){
1091 fs_resize((void **)&addrs
, (space
+1)*sizeof(char));
1095 strncat(addrs
, ",", space
+1-1-strlen(addrs
));
1096 addrs
[space
-1] = '\0';
1099 strncat(addrs
, encoded
, space
+1-1-strlen(addrs
));
1100 addrs
[space
-1] = '\0';
1101 used
+= (strlen(encoded
) + 1);
1102 fs_give((void **)&encoded
);
1106 else if(!strucmp(tag
, "note") || !strucmp(tag
, "misc")){
1108 escval
= vcard_unescape(value
);
1109 encoded
= rfc1522_encode(tmp_20k_buf
, SIZEOF_20KBUF
,
1110 (unsigned char *)escval
,
1111 (altcharset
&& *altcharset
) ? altcharset
1112 : (charset
&& *charset
)
1114 : ps_global
->posting_charmap
);
1118 fs_give((void **)&comment
);
1120 comment
= cpystr(encoded
);
1124 else if(!strucmp(tag
, "title")){
1126 escval
= vcard_unescape(value
);
1127 encoded
= rfc1522_encode(tmp_20k_buf
, SIZEOF_20KBUF
,
1128 (unsigned char *)escval
,
1129 (altcharset
&& *altcharset
) ? altcharset
1130 : (charset
&& *charset
)
1132 : ps_global
->posting_charmap
);
1136 fs_give((void **)&title
);
1138 title
= cpystr(encoded
);
1142 else if(!strucmp(tag
, "x-fcc")){
1144 escval
= vcard_unescape(value
);
1145 encoded
= rfc1522_encode(tmp_20k_buf
, SIZEOF_20KBUF
,
1146 (unsigned char *)escval
,
1147 (altcharset
&& *altcharset
) ? altcharset
1148 : (charset
&& *charset
)
1150 : ps_global
->posting_charmap
);
1154 fs_give((void **)&fcc
);
1156 fcc
= cpystr(encoded
);
1160 else if(!strucmp(tag
, "fn") || !strucmp(tag
, "cn")){
1162 escval
= vcard_unescape(value
);
1163 encoded
= rfc1522_encode(tmp_20k_buf
, SIZEOF_20KBUF
,
1164 (unsigned char *)escval
,
1165 (altcharset
&& *altcharset
) ? altcharset
1166 : (charset
&& *charset
)
1168 : ps_global
->posting_charmap
);
1172 fs_give((void **)&fullname
);
1174 fullname
= cpystr(encoded
);
1178 else if(!strucmp(tag
, "n")){
1180 * This is a structured name field. It has up to five
1181 * fields separated by ';'. The fields are Family Name (which
1182 * we'll take to be Last name for our purposes), Given Name
1183 * (First name for us), additional names, prefixes, and
1184 * suffixes. We'll ignore prefixes and suffixes.
1186 * If we find one of these records we'll use it in preference
1187 * to the formatted name field (fn).
1189 if(*value
&& *value
!= ';'){
1190 char *last
, *first
, *middle
= NULL
, *rest
= NULL
;
1191 char *esclast
, *escfirst
, *escmiddle
;
1192 static char a_semi
= ';';
1198 while(p
&& (first
= strindex(p
, a_semi
)) != NULL
){
1199 if(first
> last
&& first
[-1] != '\\')
1208 if(first
&& *(first
+1) && *(first
+1) != a_semi
){
1213 while(p
&& (middle
= strindex(p
, a_semi
)) != NULL
){
1214 if(middle
> first
&& middle
[-1] != '\\')
1223 if(middle
&& *(middle
+1) && *(middle
+1) != a_semi
){
1228 while(p
&& (rest
= strindex(p
, a_semi
)) != NULL
){
1229 if(rest
> middle
&& rest
[-1] != '\\')
1235 /* we don't care about the rest */
1246 * Structured name pieces can have multiple values.
1247 * We're just going to take the first value in each part.
1248 * Skip excaped commas, though.
1251 while(p
&& (comma
= strindex(p
, a_comma
)) != NULL
){
1252 if(comma
> last
&& comma
[-1] != '\\'){
1261 while(p
&& (comma
= strindex(p
, a_comma
)) != NULL
){
1262 if(comma
> first
&& comma
[-1] != '\\'){
1271 while(p
&& (comma
= strindex(p
, a_comma
)) != NULL
){
1272 if(comma
> middle
&& comma
[-1] != '\\'){
1280 esclast
= vcard_unescape(last
);
1281 escfirst
= vcard_unescape(first
);
1282 escmiddle
= vcard_unescape(middle
);
1283 snprintf(tmp_20k_buf
+10000, SIZEOF_20KBUF
-10000, "%s%s%s%s%s",
1284 esclast
? esclast
: "",
1285 escfirst
? ", " : "",
1286 escfirst
? escfirst
: "",
1287 escmiddle
? " " : "",
1288 escmiddle
? escmiddle
: "");
1289 tmp_20k_buf
[SIZEOF_20KBUF
-1] = '\0';
1291 encoded
= rfc1522_encode(tmp_20k_buf
, SIZEOF_20KBUF
-10000,
1292 (unsigned char *)tmp_20k_buf
+10000,
1293 (altcharset
&& *altcharset
) ? altcharset
1294 : (charset
&& *charset
)
1296 : ps_global
->posting_charmap
);
1297 tmp_20k_buf
[SIZEOF_20KBUF
-10000-1] = '\0';
1299 if(esclast
&& *esclast
&& escfirst
&& *escfirst
){
1301 fs_give((void **)&struct_name
);
1303 struct_name
= cpystr(encoded
);
1306 /* in case we don't get a fullname better than this */
1308 fullname
= cpystr(encoded
);
1312 fs_give((void **)&esclast
);
1314 fs_give((void **)&escfirst
);
1316 fs_give((void **)&escmiddle
);
1319 /* suggested nickname */
1320 else if(!strucmp(tag
, "nickname") || !strucmp(tag
, "x-nickname")){
1324 fs_give((void **)&nickname
);
1327 * Nickname can have multiple values. We're just
1328 * going to take the first. Skip escaped commas, though.
1331 while(p
&& (comma
= strindex(p
, a_comma
)) != NULL
){
1332 if(comma
> value
&& comma
[-1] != '\\'){
1340 nickname
= vcard_unescape(value
);
1345 fs_give((void **)&tag
);
1348 fs_give((void **)&altcharset
);
1351 fs_give((void **)&decoded
);
1354 fs_give((void **)&escval
);
1361 fs_give((void **)&title
);
1364 if(fullname
|| struct_name
|| nickname
|| fcc
1365 || comment
|| (addrs
&& *addrs
))
1366 selected
+= vcard_to_ta(addrs
, fullname
, struct_name
, nickname
,
1367 comment
, fcc
, ta_list
);
1370 fs_give((void **)&addrs
);
1372 free_list_array(&lines
);
1380 cmp_swoop_list(const qsort_t
*a1
, const qsort_t
*a2
)
1382 SWOOP_S
*x
= (SWOOP_S
*)a1
;
1383 SWOOP_S
*y
= (SWOOP_S
*)a2
;
1387 return((x
->dst_enum
> y
->dst_enum
) ? -1
1388 : (x
->dst_enum
== y
->dst_enum
) ? 0 : 1);
1395 return((x
> y
) ? -1 : (x
== y
) ? 0 : 1); /* doesn't matter */
1401 * Take the split out contents of a vCard entry and turn them into a TA.
1403 * Always returns 1, the number of TAs added to ta_list.
1406 vcard_to_ta(char *addrs
, char *fullname
, char *better_fullname
, char *nickname
,
1407 char *comment
, char *fcc
, TA_S
**ta_list
)
1409 ADDRESS
*addrlist
= NULL
;
1412 * Parse it into an addrlist, which is what fill_in_ta wants
1415 rfc822_parse_adrlist(&addrlist
, addrs
, fakedomain
);
1417 addrlist
= mail_newaddr(); /* empty addr, to make right thing happen */
1419 *ta_list
= fill_in_ta(ta_list
, addrlist
, 1,
1421 : better_fullname
? better_fullname
1422 : "Forwarded Entry");
1423 (*ta_list
)->nickname
= nickname
? nickname
: cpystr("");
1424 (*ta_list
)->comment
= comment
;
1425 (*ta_list
)->fcc
= fcc
;
1428 * We are tempted to want to call switch_to_last_comma_first() here when
1429 * we don't have a better_fullname (which is already last, first).
1430 * But, since this is the way it was sent to us we should probably leave
1431 * it alone. That means that if we use a fullname culled from the
1432 * body of a message, or from a header, or from an "N" vcard record,
1433 * we will make it be Last, First; but if we use one from an "FN" record
1436 (*ta_list
)->fullname
= better_fullname
? better_fullname
1439 if((*ta_list
)->nickname
)
1440 convert_possibly_encoded_str_to_utf8(&(*ta_list
)->nickname
);
1442 if((*ta_list
)->comment
)
1443 convert_possibly_encoded_str_to_utf8(&(*ta_list
)->comment
);
1446 convert_possibly_encoded_str_to_utf8(&(*ta_list
)->fcc
);
1448 if((*ta_list
)->fullname
)
1449 convert_possibly_encoded_str_to_utf8(&(*ta_list
)->fullname
);
1452 * The TA list will free its members but we have to take care of this
1453 * extra one that isn't assigned to a TA member.
1455 if(better_fullname
&& fullname
)
1456 fs_give((void **)&fullname
);
1459 mail_free_address(&addrlist
);
1466 * Look through line which is supposed to look like
1468 * type;charset=iso-8859-1;encoding=b: value
1470 * Type might be email, or nickname, ... It is optional because of the
1471 * defaulttype parameter (there isn't such a thing as defaulttype parameters
1472 * as of Nov. 96 draft). The semicolon and colon are special chars. Each
1473 * parameter in this line is a semicolon followed by the parameter type "="
1474 * the parameter value. Parameters are optional, too. There is always a colon,
1475 * followed by value. Whitespace can be everywhere up to where value starts.
1476 * There is also an optional <group> "." preceding the type, which we will
1477 * ignore. It's for grouping related lines.
1478 * (As of July, 97 draft, it is no longer permissible to include a charset
1479 * parameter in each line. Only one is permitted in the content-type mime hdr.)
1480 * (Also as of July, 97 draft, all white space is supposed to be significant.
1481 * We will continue to skip white space surrounding '=' and so forth for
1482 * backwards compatibility and because it does no harm in almost all cases.)
1483 * (As of Nov, 97 draft, some white space is not significant. That is, it is
1484 * allowed in some places.)
1487 * Args: line -- the line to look at
1488 * type -- this is a return value, and is an allocated copy of
1489 * the type, freed by the caller. For example, "email".
1490 * alt -- this is a return value, and is an allocated copy of
1491 * the value of the alternate charset, to be freed by
1492 * the caller. For example, this might be "iso-8859-2".
1493 * encoded -- this is a return value, and is set to 1 if the value
1496 * Return value: a pointer to the start of value, or NULL if the syntax
1497 * isn't right. It's possible for value to be equal to "".
1500 getaltcharset(char *line
, char **type
, char **alt
, int *encoded
)
1502 char *p
, *q
, *left_semi
, *group_dot
, *colon
;
1503 char *start_of_enc
, *start_of_cset
, tmpsave
;
1504 static char *cset
= "charset";
1505 static char *enc
= "encoding";
1516 colon
= strindex(line
, ':');
1520 left_semi
= strindex(line
, ';');
1521 if(left_semi
&& left_semi
> colon
)
1524 group_dot
= strindex(line
, '.');
1525 if(group_dot
&& (group_dot
> colon
|| (left_semi
&& group_dot
> left_semi
)))
1529 * Type is everything up to the semicolon, or the colon if no semicolon.
1530 * However, we want to skip optional <group> ".".
1533 q
= left_semi
? left_semi
: colon
;
1536 *type
= cpystr(group_dot
? group_dot
+1 : line
);
1542 && (p
= srchstr(left_semi
+1, cset
))
1545 p
= skip_white_space(p
);
1547 p
= skip_white_space(p
);
1551 while(p
< colon
&& !isspace((unsigned char)*p
) && *p
!= ';')
1556 *alt
= cpystr(start_of_cset
);
1562 if(encoded
&& left_semi
1563 && (p
= srchstr(left_semi
+1, enc
))
1566 p
= skip_white_space(p
);
1568 p
= skip_white_space(p
);
1572 while(p
< colon
&& !isspace((unsigned char)*p
) && *p
!= ';')
1575 if(*start_of_enc
== 'b' && start_of_enc
+ 1 == p
)
1583 return(skip_white_space(p
));
1588 * Return incoming_fullname in Last, First form.
1589 * The passed in fullname should already be in UTF-8.
1590 * If there is already a comma, we add quotes around the fullname so we can
1591 * tell it isn't a Last, First comma but a real comma in the Fullname.
1593 * Args: incoming_fullname --
1594 * new_full -- passed in pointer to place to put new fullname
1595 * nbuf -- size of new_full
1599 switch_to_last_comma_first(char *incoming_fullname
, char *new_full
, size_t nbuf
)
1602 char *save_end
, *nf
, *inc
, *p
= NULL
;
1607 if(incoming_fullname
== NULL
){
1612 /* get rid of leading white space */
1613 for(inc
= incoming_fullname
; *inc
&& isspace((unsigned char)*inc
); inc
++)
1616 /* get rid of trailing white space */
1617 for(p
= inc
+ strlen(inc
) - 1; *p
&& p
>= inc
&&
1618 isspace((unsigned char)*p
); p
--)
1622 if(save_end
== inc
){
1627 save_value
= *save_end
; /* so we don't alter incoming_fullname */
1630 memset(new_full
, 0, nbuf
); /* need this the way it is done below */
1632 if(strindex(inc
, ',') != NULL
){
1636 * Quote already existing comma.
1638 * We'll get this wrong if it is already quoted but the quote
1639 * doesn't start right at the beginning.
1643 if(nf
-new_full
< nbuf
-1)
1647 strncpy(nf
, inc
, MIN(nbuf
- (add_quotes
? 3 : 1), nbuf
-(nf
-new_full
)-1));
1648 new_full
[nbuf
-1] = '\0';
1650 strncat(nf
, "\"", nbuf
-(nf
-new_full
)-1);
1651 new_full
[nbuf
-1] = '\0';
1654 else if(strindex(inc
, SPACE
)){
1657 end
= new_full
+ nbuf
- 1;
1660 * Switch First Middle Last to Last, First Middle
1663 /* last points to last word, which does exist */
1664 last
= skip_white_space(strrindex(inc
, SPACE
));
1667 for(p
= last
; *p
&& nf
< end
-2 && nf
-new_full
< nbuf
; *nf
++ = *p
++)
1670 if(nf
-new_full
< nbuf
-1)
1673 if(nf
-new_full
< nbuf
-1)
1676 /* copy First Middle */
1677 for(p
= inc
; p
< last
&& nf
< end
&& nf
-new_full
< nbuf
-1; *nf
++ = *p
++)
1680 new_full
[nbuf
-1] = '\0';
1682 removing_trailing_white_space(new_full
);
1685 strncpy(new_full
, inc
, nbuf
-1);
1687 new_full
[nbuf
-1] = '\0';
1689 *save_end
= save_value
;
1694 * Fetch this body part, content-decode it if necessary, split it into
1695 * lines, and return the lines in an allocated array, which is freed
1696 * by the caller. Folded lines are replaced by single long lines.
1699 detach_vcard_att(MAILSTREAM
*stream
, long int msgno
, struct mail_bodystruct
*body
, char *partnum
)
1701 unsigned long length
;
1702 char *text
= NULL
, /* raw text */
1703 *dtext
= NULL
, /* content-decoded text */
1704 **res
= NULL
, /* array of decoded lines */
1705 *lptr
, /* line pointer */
1709 /* make our own copy of text so we can change it */
1710 text
= cpystr(mail_fetchbody_full(stream
, msgno
, partnum
, &length
,FT_PEEK
));
1711 text
[length
] = '\0';
1713 /* decode the text */
1714 switch(body
->encoding
){
1723 dtext
= (char *)rfc822_base64((unsigned char *)text
,
1724 (unsigned long)strlen(text
),
1727 dtext
[length
] = '\0';
1729 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
1730 "Malformed B64 data in address book attachment.");
1733 fs_give((void **)&text
);
1737 case ENCQUOTEDPRINTABLE
:
1738 dtext
= (char *)rfc822_qprint((unsigned char *)text
,
1739 (unsigned long)strlen(text
),
1742 dtext
[length
] = '\0';
1744 q_status_message(SM_ORDER
| SM_DING
, 3, 4,
1745 "Malformed QP data in address book attachment.");
1748 fs_give((void **)&text
);
1753 /* count the lines */
1756 for(lptr
= next_line
; lptr
&& *lptr
; lptr
= next_line
){
1757 for(next_line
= lptr
; *next_line
; next_line
++){
1759 * Find end of current line.
1761 if(*next_line
== '\r' && *(next_line
+1) == '\n'){
1763 /* not a folded line, count it */
1764 if(!*next_line
|| (*next_line
!= SPACE
&& *next_line
!= TAB
))
1772 /* allocate space for resulting array of lines */
1773 res
= (char **)fs_get((count
+ 1) * sizeof(char *));
1774 memset((void *)res
, 0, (count
+ 1) * sizeof(char *));
1776 for(i
=0, lptr
=next_line
; lptr
&& *lptr
&& i
< count
; lptr
=next_line
, i
++){
1778 * Move next_line to start of next line and null terminate
1781 for(next_line
= lptr
; *next_line
; next_line
++){
1783 * Find end of current line.
1785 if(*next_line
== '\r' && *(next_line
+1) == '\n'){
1787 /* not a folded line, terminate it */
1788 if(!*next_line
|| (*next_line
!= SPACE
&& *next_line
!= TAB
)){
1789 *(next_line
-2) = '\0';
1795 /* turn folded lines into long lines in place */
1797 res
[i
] = cpystr(lptr
);
1801 fs_give((void **)&dtext
);
1809 * Look for possible addresses in the first text part of a message for
1810 * use by TakeAddr command.
1811 * Returns the number of TA lines added.
1814 grab_addrs_from_body(MAILSTREAM
*stream
, long int msgno
,
1815 struct mail_bodystruct
*body
, TA_S
**ta_list
)
1817 #define MAXLINESZ 2000
1818 char line
[MAXLINESZ
+ 1];
1821 SourceType src
= CharStar
;
1822 int added
= 0, failure
;
1824 ATTACH_S
*a
= ps_global
->atmts
;
1826 dprint((9, "\n - grab_addrs_from_body - \n"));
1829 * If it is text/plain or it is multipart with a first part of text/plain,
1830 * we want to continue, else forget it.
1832 if(!((body
->type
== TYPETEXT
&& body
->subtype
1833 && ALLOWED_SUBTYPE(body
->subtype
))
1835 (body
->type
== TYPEMULTIPART
&& body
->nested
.part
1836 && body
->nested
.part
->body
.type
== TYPETEXT
1837 && ALLOWED_SUBTYPE(body
->nested
.part
->body
.subtype
))))
1844 if((so
= so_get(src
, NULL
, EDIT_ACCESS
)) == NULL
)
1847 gf_set_so_writec(&pc
, so
);
1849 if(body
->type
== TYPEMULTIPART
1850 && strucmp(body
->subtype
, "ALTERNATIVE") == 0
1851 && a
&& a
+1 && (a
+1)->shown
)
1854 failure
= !get_body_part_text(stream
, body
, msgno
, partno
, 0L, pc
,
1855 NULL
, NULL
, GBPT_NONE
);
1857 gf_clear_so_writec(so
);
1866 while(get_line_of_message(so
, line
, sizeof(line
))){
1868 char save_end
, *start
, *tmp_a_string
, *tmp_personal
, *p
;
1871 /* process each @ in the line */
1872 for(p
= (char *) line
; (start
= mail_addr_scan(p
, &n
)); p
= start
+ n
){
1874 tmp_personal
= NULL
;
1876 if(start
> line
&& *(start
-1) == '<' && *(start
+n
) == '>'){
1877 int words
, in_quote
;
1881 * Take a shot at looking for full name
1882 * If we can find a colon maybe we've got a header line
1883 * embedded in the body.
1884 * This is real ad hoc.
1888 * Go back until we run into a character that probably
1889 * isn't a fullname character.
1892 in_quote
= words
= 0;
1893 while(fn_start
> p
&& (in_quote
|| !(*(fn_start
-1) == ':'
1894 || *(fn_start
-1) == ';' || *(fn_start
-1) == ','))){
1896 if(!in_quote
&& isspace((unsigned char)*fn_start
))
1898 else if(*fn_start
== '"' &&
1899 (fn_start
== p
|| *(fn_start
-1) != '\\')){
1912 /* wasn't a real quote, forget about fullname */
1916 /* Skip forward over the white space. */
1917 while(isspace((unsigned char)*fn_start
) || *fn_start
== '(')
1921 * Make sure the first word is capitalized.
1922 * (just so it is more likely it is a name)
1924 while(fn_start
< start
-1 &&
1925 !(isupper(*fn_start
) || *fn_start
== '"')){
1926 if(*fn_start
== '('){
1931 /* skip forward over this word */
1932 while(fn_start
< start
-1 &&
1933 !isspace((unsigned char)*fn_start
))
1936 while(fn_start
< start
-1 &&
1937 isspace((unsigned char)*fn_start
))
1941 if(fn_start
< start
-1){
1942 char *fn_end
, save_fn_end
;
1944 /* remove white space between fullname and start */
1946 while(fn_end
> fn_start
1947 && isspace((unsigned char)*(fn_end
- 1)))
1950 save_fn_end
= *fn_end
;
1953 /* remove matching quotes */
1954 if(*fn_start
== '"' && *(fn_end
-1) == '"'){
1956 *fn_end
= save_fn_end
;
1958 save_fn_end
= *fn_end
;
1964 tmp_personal
= cpystr(fn_start
);
1966 *fn_end
= save_fn_end
;
1971 save_end
= *(start
+n
);
1973 /* rfc822_parse_adrlist feels free to destroy input so send copy */
1974 tmp_a_string
= cpystr(start
);
1975 *(start
+n
) = save_end
;
1977 ps_global
->c_client_error
[0] = '\0';
1978 rfc822_parse_adrlist(&addr
, tmp_a_string
, fakedomain
);
1980 fs_give((void **)&tmp_a_string
);
1985 fs_give((void **)&addr
->personal
);
1987 addr
->personal
= tmp_personal
;
1990 fs_give((void **)&tmp_personal
);
1993 if((addr
&& addr
->host
&& addr
->host
[0] == '@') ||
1994 ps_global
->c_client_error
[0]){
1995 mail_free_address(&addr
);
1999 if(addr
&& addr
->mailbox
&& addr
->host
){
2001 *ta_list
= fill_in_ta(ta_list
, addr
, 0, (char *)NULL
);
2005 mail_free_address(&addr
);
2015 * Get the next line of the object pointed to by source.
2016 * Skips empty lines.
2017 * Linebuf is a passed in buffer to put the line in.
2018 * Linebuf is null terminated and \r and \n chars are removed.
2019 * 0 is returned when there is no more input.
2022 get_line_of_message(STORE_S
*source
, char *linebuf
, int linebuflen
)
2030 while(so_readc(&c
, source
)){
2031 if(c
== '\n' || c
== '\r'){
2037 if(pos
>= linebuflen
- 2)
2042 linebuf
[pos
] = '\0';
2049 * Inserts a new entry based on addr in the TA list.
2050 * Makes sure everything is UTF-8. That is,
2051 * Values used for strvalue will be converted to UTF-8 first.
2052 * Personal name fields of addr args will be converted to UTF-8.
2054 * Args: old_current -- the TA list
2055 * addr -- the address for this line
2056 * checked -- start this line out checked
2057 * print_string -- if non-null, this line is informational and is just
2058 * printed out, it can't be selected
2061 fill_in_ta(TA_S
**old_current
, struct mail_address
*addr
, int checked
, char *print_string
)
2065 /* c-client convention for group syntax, which we don't want to deal with */
2066 if(!print_string
&& (!addr
|| !addr
->mailbox
|| !addr
->host
))
2067 new_current
= *old_current
;
2070 new_current
= new_taline(old_current
);
2071 if(print_string
&& addr
){ /* implied List when here */
2072 new_current
->frwrded
= 1;
2073 new_current
->skip_it
= 0;
2074 new_current
->print
= 0;
2075 new_current
->checked
= checked
!= 0;
2076 new_current
->addr
= copyaddrlist(addr
);
2077 decode_addr_names_to_utf8(new_current
->addr
);
2078 new_current
->strvalue
= cpystr(print_string
);
2079 convert_possibly_encoded_str_to_utf8(&new_current
->strvalue
);
2081 else if(print_string
){
2082 new_current
->frwrded
= 0;
2083 new_current
->skip_it
= 1;
2084 new_current
->print
= 1;
2085 new_current
->checked
= 0;
2086 new_current
->addr
= 0;
2087 new_current
->strvalue
= cpystr(print_string
);
2088 convert_possibly_encoded_str_to_utf8(&new_current
->strvalue
);
2091 new_current
->frwrded
= 0;
2092 new_current
->skip_it
= 0;
2093 new_current
->print
= 0;
2094 new_current
->checked
= checked
!= 0;
2095 new_current
->addr
= copyaddr(addr
);
2096 decode_addr_names_to_utf8(new_current
->addr
);
2097 if(new_current
->addr
&& new_current
->addr
->personal
)
2098 remove_quotes(new_current
->addr
->personal
);
2099 if(addr
->host
[0] == '.')
2100 new_current
->strvalue
= cpystr("Error in address (ok to try Take anyway)");
2105 len
= est_size(new_current
->addr
);
2106 bufp
= (char *) fs_get(len
* sizeof(char));
2107 new_current
->strvalue
= cpystr(addr_string(new_current
->addr
, bufp
, len
));
2108 fs_give((void **) &bufp
);
2111 convert_possibly_encoded_str_to_utf8(&new_current
->strvalue
);
2115 return(new_current
);
2120 eliminate_dups_and_us(TA_S
*list
)
2122 return(eliminate_dups_and_maybe_us(list
, 1));
2127 eliminate_dups_but_not_us(TA_S
*list
)
2129 return(eliminate_dups_and_maybe_us(list
, 0));
2134 * Look for dups in list and mark them so we'll skip them.
2136 * We also throw out any addresses that are us (if us_too is set), since
2137 * we won't usually want to take ourselves to the addrbook.
2138 * On the otherhand, if there is nothing but us, we leave it.
2140 * Don't toss out forwarded entries.
2142 * Returns the number of dups eliminated that were also selected.
2145 eliminate_dups_and_maybe_us(TA_S
*list
, int us_too
)
2152 for(ta
= list
; ta
; ta
= ta
->next
){
2154 if(ta
->skip_it
|| ta
->frwrded
) /* already tossed or forwarded */
2159 /* Check addr "a" versus tail of the list */
2160 for(tb
= ta
->next
; tb
; tb
= tb
->next
){
2161 if(tb
->skip_it
|| tb
->frwrded
) /* already tossed or forwarded */
2165 if(dup_addrs(a
, b
)){
2166 if(ta
->checked
|| !(tb
->checked
)){
2173 else{ /* tb->checked */
2183 /* check whether all remaining addrs are us */
2184 for(ta
= list
; ta
; ta
= ta
->next
){
2186 if(ta
->skip_it
) /* already tossed */
2189 if(ta
->frwrded
) /* forwarded entry, so not us */
2194 if(!address_is_us(a
, ps_global
))
2199 * if at least one address that isn't us, remove all of us from
2203 for(ta
= list
; ta
; ta
= ta
->next
){
2205 if(ta
->skip_it
|| ta
->frwrded
) /* already tossed or forwarded */
2210 if(address_is_us(a
, ps_global
)){
2226 * Returns 1 if x and y match, 0 if not.
2229 dup_addrs(struct mail_address
*x
, struct mail_address
*y
)
2231 return(x
&& y
&& strucmp(x
->mailbox
, y
->mailbox
) == 0
2232 && strucmp(x
->host
, y
->host
) == 0);