/* * ======================================================================== * Copyright 2006-2007 University of Washington * Copyright 2013-2021 Eduardo Chappa * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * ======================================================================== */ /*====================================================================== takeaddr.c Mostly support for Take Address command. ====*/ #include "../pith/headers.h" #include "../pith/takeaddr.h" #include "../pith/conf.h" #include "../pith/bldaddr.h" #include "../pith/adrbklib.h" #include "../pith/copyaddr.h" #include "../pith/addrstring.h" #include "../pith/status.h" #include "../pith/mailview.h" #include "../pith/reply.h" #include "../pith/url.h" #include "../pith/mailpart.h" #include "../pith/sequence.h" #include "../pith/stream.h" #include "../pith/busy.h" #include "../pith/ablookup.h" #include "../pith/list.h" static char *fakedomain = "@"; long msgno_for_pico_callback; BODY *body_for_pico_callback = NULL; ENVELOPE *env_for_pico_callback = NULL; /* * Previous selectable TA line. * skips over the elements with skip_it set */ TA_S * pre_sel_taline(TA_S *current) { TA_S *p; if(!current) return NULL; p = current->prev; while(p && p->skip_it) p = p->prev; return(p); } /* * Previous TA line, selectable or just printable. * skips over the elements with skip_it set */ TA_S * pre_taline(TA_S *current) { TA_S *p; if(!current) return NULL; p = current->prev; while(p && (p->skip_it && !p->print)) p = p->prev; return(p); } /* * Next selectable TA line. * skips over the elements with skip_it set */ TA_S * next_sel_taline(TA_S *current) { TA_S *p; if(!current) return NULL; p = current->next; while(p && p->skip_it) p = p->next; return(p); } /* * Next TA line, including print only lines. * skips over the elements with skip_it set unless they are print lines */ TA_S * next_taline(TA_S *current) { TA_S *p; if(!current) return NULL; p = current->next; while(p && (p->skip_it && !p->print)) p = p->next; return(p); } /* * Mark all of the addresses with a check. * * Args: f_line -- the first ta line * * Returns the number of lines checked. */ int ta_mark_all(TA_S *f_line) { TA_S *ctmp; int how_many_selected = 0; for(ctmp = f_line; ctmp; ctmp = next_sel_taline(ctmp)){ ctmp->checked = 1; how_many_selected++; } return(how_many_selected); } /* * Does the takeaddr list consist of a single address? * * Args: f_line -- the first ta line * * Returns 1 if only one address and 0 otherwise. */ int is_talist_of_one(TA_S *f_line) { if(f_line == NULL) return 0; /* there is at least one, see if there are two */ if(next_sel_taline(f_line) != NULL) return 0; return 1; } /* * Turn off all of the check marks. * * Args: f_line -- the first ta line * * Returns the number of lines checked (0). */ int ta_unmark_all(TA_S *f_line) { TA_S *ctmp; for(ctmp = f_line; ctmp; ctmp = ctmp->next) ctmp->checked = 0; return 0; } /* * new_taline - create new TA_S, zero it out, and insert it after current. * NOTE current gets set to the new TA_S, too! */ TA_S * new_taline(TA_S **current) { TA_S *p; p = (TA_S *)fs_get(sizeof(TA_S)); memset((void *)p, 0, sizeof(TA_S)); if(current){ if(*current){ p->next = (*current)->next; (*current)->next = p; p->prev = *current; if(p->next) p->next->prev = p; } *current = p; } return(p); } void free_taline(TA_S **p) { if(p && *p){ if((*p)->addr) mail_free_address(&(*p)->addr); if((*p)->strvalue) fs_give((void **)&(*p)->strvalue); if((*p)->nickname) fs_give((void **)&(*p)->nickname); if((*p)->fullname) fs_give((void **)&(*p)->fullname); if((*p)->fcc) fs_give((void **)&(*p)->fcc); if((*p)->comment) fs_give((void **)&(*p)->comment); if((*p)->prev) (*p)->prev->next = (*p)->next; if((*p)->next) (*p)->next->prev = (*p)->prev; fs_give((void **)p); } } void free_talines(TA_S **ta_list) { TA_S *current, *ctmp; if(ta_list && (current = (*ta_list))){ while(current->prev) current = current->prev; while(current){ ctmp = current->next; free_taline(¤t); current = ctmp; } *ta_list = NULL; } } void free_ltline(LINES_TO_TAKE **p) { if(p && *p){ if((*p)->printval) fs_give((void **)&(*p)->printval); if((*p)->exportval) fs_give((void **)&(*p)->exportval); if((*p)->prev) (*p)->prev->next = (*p)->next; if((*p)->next) (*p)->next->prev = (*p)->prev; fs_give((void **)p); } } void free_ltlines(LINES_TO_TAKE **lt_list) { LINES_TO_TAKE *current, *ctmp; if(lt_list && (current = (*lt_list))){ while(current->prev) current = current->prev; while(current){ ctmp = current->next; free_ltline(¤t); current = ctmp; } *lt_list = NULL; } } /* * Return the first selectable TakeAddr line. * * Args: q -- any line in the list */ TA_S * first_sel_taline(TA_S *q) { TA_S *first; if(q == NULL) return NULL; first = NULL; /* back up to the head of the list */ while(q){ first = q; q = pre_sel_taline(q); } /* * If first->skip_it, that means we were already past the first * legitimate line, so we have to look in the other direction. */ if(first->skip_it) first = next_sel_taline(first); return(first); } /* * Return the last selectable TakeAddr line. * * Args: q -- any line in the list */ TA_S * last_sel_taline(TA_S *q) { TA_S *last; if(q == NULL) return NULL; last = NULL; /* go to the end of the list */ while(q){ last = q; q = next_sel_taline(q); } /* * If last->skip_it, that means we were already past the last * legitimate line, so we have to look in the other direction. */ if(last->skip_it) last = pre_sel_taline(last); return(last); } /* * Return the first TakeAddr line, selectable or just printable. * * Args: q -- any line in the list */ TA_S * first_taline(TA_S *q) { TA_S *first; if(q == NULL) return NULL; first = NULL; /* back up to the head of the list */ while(q){ first = q; q = pre_taline(q); } /* * If first->skip_it, that means we were already past the first * legitimate line, so we have to look in the other direction. */ if(first->skip_it && !first->print) first = next_taline(first); return(first); } /* * Find the first TakeAddr line which is checked, beginning with the * passed in line. * * Args: head -- A passed in TakeAddr line, usually will be the first * * Result: returns a pointer to the first checked line. * NULL if no such line */ TA_S * first_checked(TA_S *head) { TA_S *p; p = head; for(p = head; p; p = next_sel_taline(p)) if(p->checked && !p->skip_it) break; return(p); } /* * Form a list of strings which are addresses to go in a list. * These are entries in a list, so can be full rfc822 addresses. * The strings are allocated here. * * Args: head -- A passed in TakeAddr line, usually will be the first * * Result: returns an allocated array of pointers to allocated strings */ char ** list_of_checked(TA_S *head) { TA_S *p; int count; char **list, **cur; ADDRESS *a; /* first count them */ for(p = head, count = 0; p; p = next_sel_taline(p)){ if(p->checked && !p->skip_it){ if(p->frwrded){ /* * Remove fullname, fcc, comment, and nickname since not * appropriate for list values. */ if(p->fullname) fs_give((void **)&p->fullname); if(p->fcc) fs_give((void **)&p->fcc); if(p->comment) fs_give((void **)&p->comment); if(p->nickname) fs_give((void **)&p->nickname); for(a = p->addr; a; a = a->next) count++; } else{ /* * Don't even attempt to include bogus addresses in * the list. If the user wants to get at those, they * have to try taking only that single address. */ if(p->addr && p->addr->host && p->addr->host[0] == '.') p->skip_it = 1; else count++; } } } /* allocate pointers */ list = (char **)fs_get((count + 1) * sizeof(char *)); memset((void *)list, 0, (count + 1) * sizeof(char *)); cur = list; /* allocate and point to address strings */ for(p = head; p; p = next_sel_taline(p)){ if(p->checked && !p->skip_it){ if(p->frwrded) for(a = p->addr; a; a = a->next){ ADDRESS *next_addr; char *bufp; size_t len; next_addr = a->next; a->next = NULL; len = est_size(a); bufp = (char *) fs_get(len * sizeof(char)); *cur++ = cpystr(addr_string(a, bufp, len)); a->next = next_addr; fs_give((void **)&bufp); } else *cur++ = cpystr(p->strvalue); } } return(list); } /* jpf: This used to be the guts of cmd_take_addr, but I made this * function so I could generalize with WP. It still has some display * stuff that we need make sure not to do when in WP mode. * * Set things up for when we take addresses * * Args: ps -- pine state * msgmap -- the MessageMap * ta_ret -- the take addr lines * selected_num -- the number of selectable addresses * flags -- takeaddr flags, like whether or not * we're doing aggs, and whether to do prompts * * Returns: -1 on "failure" */ int set_up_takeaddr(int cmd, struct pine *ps, MSGNO_S *msgmap, TA_S **ta_ret, int *selected_num, int flags, int (*att_addr_f)(TA_S *, int)) { long i; ENVELOPE *env = NULL; int how_many_selected = 0, added, rtype = 0, we_cancel = 0, special_processing = 0, select_froms = 0; TA_S *current = NULL, *prev_comment_line, *ta; BODY **body_h, *special_body = NULL, *body = NULL; dprint((2, "\n - taking address into address book - \n")); if(!(cmd == 'a' || cmd == 'e')) return -1; if((flags & TA_AGG) && !pseudo_selected(ps_global->mail_stream, msgmap)) return -1; if(mn_get_total(msgmap) > 0 && mn_total_cur(msgmap) == 1) special_processing = 1; if(flags & TA_AGG) select_froms = 1; /* this is a non-selectable label */ current = fill_in_ta(¤t, (ADDRESS *) NULL, 0, /* TRANSLATORS: This is a heading describing some addresses the user will have the chance to choose from. */ _(" These entries are taken from the attachments ")); prev_comment_line = current; /* * Add addresses from special attachments for each message. */ added = 0; for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap)){ env = pine_mail_fetchstructure(ps->mail_stream, mn_m2raw(msgmap, i), &body); if(!env){ q_status_message(SM_ORDER | SM_DING, 3, 4, _("Can't take address into address book. Error accessing folder")); rtype = -1; goto doneskee; } added += process_vcard_atts(ps->mail_stream, mn_m2raw(msgmap, i), body, body, NULL, ¤t); } if(!(flags & TA_AGG) && added > 1 && att_addr_f && (*att_addr_f)(current, added) <= 0) goto doneskee; if(!(flags & TA_NOPROMPT)) we_cancel = busy_cue(NULL, NULL, 1); /* * add a comment line to separate attachment address lines * from header address lines */ if(added > 0){ current = fill_in_ta(¤t, (ADDRESS *) NULL, 0, /* TRANSLATORS: msg is message */ _(" These entries are taken from the msg headers ")); prev_comment_line = current; how_many_selected += added; select_froms = 0; } else{ /* turn off header comment, and no separator comment */ prev_comment_line->print = 0; prev_comment_line = NULL; } /* * Add addresses from headers of messages. */ for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap)){ if(special_processing) body_h = &special_body; else body_h = NULL; env = pine_mail_fetchstructure(ps->mail_stream, mn_m2raw(msgmap, i), body_h); if(!env){ if(we_cancel) cancel_busy_cue(-1); q_status_message(SM_ORDER | SM_DING, 3, 4, _("Can't take address into address book. Error accessing folder")); rtype = -1; goto doneskee; } added = add_addresses_to_talist(ps, i, "from", ¤t, env->from, select_froms); if(select_froms) how_many_selected += added; if(!address_is_same(env->from, env->reply_to)) (void)add_addresses_to_talist(ps, i, "reply-to", ¤t, env->reply_to, 0); if(!address_is_same(env->from, env->sender)) (void)add_addresses_to_talist(ps, i, "sender", ¤t, env->sender, 0); (void)add_addresses_to_talist(ps, i, "to", ¤t, env->to, 0); (void)add_addresses_to_talist(ps, i, "cc", ¤t, env->cc, 0); (void)add_addresses_to_talist(ps, i, "bcc", ¤t, env->bcc, 0); } /* * Check to see if we added an explanatory line about the * header addresses but no header addresses. If so, remove the * explanatory line. */ if(prev_comment_line){ how_many_selected -= eliminate_dups_and_us(first_sel_taline(current)); for(ta = prev_comment_line->next; ta; ta = ta->next) if(!ta->skip_it) break; /* all entries were skip_it entries, turn off print */ if(!ta){ prev_comment_line->print = 0; prev_comment_line = NULL; } } /* * If there's only one message we let the user view it using ^T * from the header editor. */ if(!(flags & TA_NOPROMPT) && special_processing && env && special_body){ msgno_for_pico_callback = mn_m2raw(msgmap, mn_first_cur(msgmap)); body_for_pico_callback = special_body; env_for_pico_callback = env; } else{ env_for_pico_callback = NULL; body_for_pico_callback = NULL; } current = fill_in_ta(¤t, (ADDRESS *)NULL, 0, _(" Below this line are some possibilities taken from the text of the msg ")); prev_comment_line = current; /* * Add addresses from the text of the body. */ added = 0; for(i = mn_first_cur(msgmap); i > 0L; i = mn_next_cur(msgmap)){ body = NULL; env = pine_mail_fetchstructure(ps->mail_stream, mn_m2raw(msgmap, i), &body); if(env && body) added += grab_addrs_from_body(ps->mail_stream, mn_m2raw(msgmap, i), body, ¤t); } if(added == 0){ prev_comment_line->print = 0; prev_comment_line = NULL; } /* * Check to see if all we added are dups. * If so, remove the comment line. */ if(prev_comment_line){ how_many_selected -= eliminate_dups_and_us(first_sel_taline(current)); for(ta = prev_comment_line->next; ta; ta = ta->next) if(!ta->skip_it) break; /* all entries were skip_it entries, turn off print */ if(!ta) prev_comment_line->print = 0; } if(we_cancel) cancel_busy_cue(-1); doneskee: env_for_pico_callback = NULL; body_for_pico_callback = NULL; ps->mangled_screen = 1; if(flags & TA_AGG) restore_selected(msgmap); *ta_ret = current; if(selected_num) *selected_num = how_many_selected; return(rtype); } int convert_ta_to_lines(TA_S *ta_list, LINES_TO_TAKE **old_current) { ADDRESS *a; TA_S *ta; LINES_TO_TAKE *new_current; char *exportval, *printval; int ret = 0; for(ta = first_sel_taline(ta_list); ta; ta = next_sel_taline(ta)){ if(ta->skip_it) continue; if(ta->frwrded){ if(ta->fullname) fs_give((void **)&ta->fullname); if(ta->fcc) fs_give((void **)&ta->fcc); if(ta->comment) fs_give((void **)&ta->comment); if(ta->nickname) fs_give((void **)&ta->nickname); } else if(ta->addr && ta->addr->host && ta->addr->host[0] == '.') ta->skip_it = 1; if(ta->frwrded){ for(a = ta->addr; a; a = a->next){ ADDRESS *next_addr; if(a->host && a->host[0] == '.') continue; next_addr = a->next; a->next = NULL; exportval = cpystr(simple_addr_string(a, tmp_20k_buf, SIZEOF_20KBUF)); if(!exportval || !exportval[0]){ if(exportval) fs_give((void **)&exportval); a->next = next_addr; continue; } printval = addr_list_string(a, NULL, 0); if(!printval || !printval[0]){ if(printval) fs_give((void **)&printval); printval = cpystr(exportval); } new_current = new_ltline(old_current); new_current->flags = LT_NONE; new_current->printval = printval; new_current->exportval = exportval; a->next = next_addr; ret++; } } else{ if(ta->addr){ exportval = cpystr(simple_addr_string(ta->addr, tmp_20k_buf, SIZEOF_20KBUF)); if(exportval && exportval[0]){ new_current = new_ltline(old_current); new_current->flags = LT_NONE; new_current->exportval = exportval; if(ta->strvalue && ta->strvalue[0]) new_current->printval = cpystr(ta->strvalue); else new_current->printval = cpystr(new_current->exportval); ret++; } else if(exportval) fs_give((void **)&exportval); } } } return(ret); } /* * new_ltline - create new LINES_TO_TAKE, zero it out, * and insert it after current. * NOTE current gets set to the new current. */ LINES_TO_TAKE * new_ltline(LINES_TO_TAKE **current) { LINES_TO_TAKE *p; p = (LINES_TO_TAKE *)fs_get(sizeof(LINES_TO_TAKE)); memset((void *)p, 0, sizeof(LINES_TO_TAKE)); if(current){ if(*current){ p->next = (*current)->next; (*current)->next = p; p->prev = *current; if(p->next) p->next->prev = p; } *current = p; } return(p); } int add_addresses_to_talist(struct pine *ps, long int msgno, char *field, TA_S **old_current, struct mail_address *adrlist, int checked) { ADDRESS *addr; int added = 0; for(addr = adrlist; addr; addr = addr->next){ if(addr->host && addr->host[0] == '.'){ char *h, *fields[2]; fields[0] = field; fields[1] = NULL; if((h = pine_fetchheader_lines(ps->mail_stream, msgno,NULL,fields)) != NULL){ char *p, fname[32]; int q; q = strlen(h); snprintf(fname, sizeof(fname), "%s:", field); for(p = h; (p = strstr(p, fname)) != NULL; ) rplstr(p, q-(p-h), strlen(fname), ""); /* strip field strings */ sqznewlines(h); /* blat out CR's & LF's */ for(p = h; (p = strchr(p, TAB)) != NULL; ) *p++ = ' '; /* turn TABs to space */ if(*h){ unsigned long l; ADDRESS *new_addr; size_t ll; new_addr = (ADDRESS *)fs_get(sizeof(ADDRESS)); memset(new_addr, 0, sizeof(ADDRESS)); /* * Base64 armor plate the gunk to protect against * c-client quoting in output. */ p = (char *)rfc822_binary((void *)h, (unsigned long)strlen(h), &l); sqznewlines(p); ll = strlen(p) + 3; new_addr->mailbox = (char *) fs_get((ll+1) * sizeof(char)); snprintf(new_addr->mailbox, ll+1, "&%s", p); fs_give((void **)&p); new_addr->host = cpystr(RAWFIELD); fill_in_ta(old_current, new_addr, 0, (char *)NULL); mail_free_address(&new_addr); } fs_give((void **)&h); } return(0); } } /* no syntax errors in list, add them all */ for(addr = adrlist; addr; addr = addr->next){ fill_in_ta(old_current, addr, checked, (char *)NULL); added++; } return(added); } /* * Look for Text/directory attachments and process them. * Return number of lines added to the ta_list. */ int process_vcard_atts(MAILSTREAM *stream, long int msgno, struct mail_bodystruct *root, struct mail_bodystruct *body, char *partnum, TA_S **ta_list) { char *addrs, /* comma-separated list of addresses */ *value, *encoded, *escval, *nickname, *fullname, *struct_name, *fcc, *tag, *decoded, *num = NULL, *defaulttype = NULL, *charset = NULL, *altcharset, *comma, *p, *comment, *title, **lines = NULL, **ll; size_t space; int used = 0, is_encoded = 0, vcard_nesting = 0, selected = 0; PART *part; PARAMETER *parm; static char a_comma = ','; /* * Look through all the subparts searching for our special type. */ if(body && body->type == TYPEMULTIPART){ for(part = body->nested.part; part; part = part->next) selected += process_vcard_atts(stream, msgno, root, &part->body, partnum, ta_list); } /* only look in rfc822 subtype of type message */ else if(body && body->type == TYPEMESSAGE && body->subtype && !strucmp(body->subtype, "rfc822")){ selected += process_vcard_atts(stream, msgno, root, body->nested.msg->body, partnum, ta_list); } /* found one (TYPEAPPLICATION for back compat.) */ else if(body && MIME_VCARD(body->type,body->subtype)){ dprint((4, "\n - found abook attachment to process - \n")); /* * The Text/directory type has a possible parameter called * "defaulttype" that we need to look for (this is from an old draft * and is supported only for backwards compatibility). There is * also a possible default "charset". (Default charset is also from * an old draft and is no longer allowed.) We don't care about any of * the other parameters. */ for(parm = body->parameter; parm; parm = parm->next) if(!strucmp("defaulttype", parm->attribute)) break; if(parm) defaulttype = parm->value; /* ...and look for possible charset parameter */ for(parm = body->parameter; parm; parm = parm->next) if(!strucmp("charset", parm->attribute)) break; if(parm) charset = parm->value; num = partnum ? cpystr(partnum) : partno(root, body); lines = detach_vcard_att(stream, msgno, body, num); if(num) fs_give((void **)&num); nickname = fullname = comment = title = fcc = struct_name = NULL; #define CHUNK (size_t)500 space = CHUNK; /* make comma-separated list of email addresses in addrs */ addrs = (char *)fs_get((space+1) * sizeof(char)); *addrs = '\0'; for(ll = lines; ll && *ll; ll++){ altcharset = NULL; decoded = NULL; escval = NULL; is_encoded = 0; value = getaltcharset(*ll, &tag, &altcharset, &is_encoded); if(!value || !tag){ if(tag) fs_give((void **)&tag); if(altcharset) fs_give((void **)&altcharset); continue; } if(is_encoded){ unsigned long length; decoded = (char *)rfc822_base64((unsigned char *)value, (unsigned long)strlen(value), &length); if(decoded){ decoded[length] = '\0'; value = decoded; } else value = ""; } /* support default tag (back compat.) */ if(*tag == '\0' && defaulttype){ fs_give((void **)&tag); tag = cpystr(defaulttype); } if(!strucmp(tag, "begin")){ /* vCard BEGIN */ vcard_nesting++; } else if(!strucmp(tag, "end")){ /* vCard END */ if(--vcard_nesting == 0){ if(title){ if(!comment) comment = title; else fs_give((void **)&title); } /* add this entry */ selected += vcard_to_ta(addrs, fullname, struct_name, nickname, comment, fcc, ta_list); if(addrs) *addrs = '\0'; used = 0; nickname = fullname = comment = title = fcc = NULL; struct_name = NULL; } } /* add another address to addrs */ else if(!strucmp(tag, "email")){ if(*value){ escval = vcard_unescape(value); encoded = encode_fullname_of_addrstring(escval, (altcharset && *altcharset) ? altcharset : (charset && *charset) ? charset : ps_global->posting_charmap); if(encoded){ /* allocate more space */ if((used + strlen(encoded) + 1) > space){ space += CHUNK; fs_resize((void **)&addrs, (space+1)*sizeof(char)); } if(*addrs){ strncat(addrs, ",", space+1-1-strlen(addrs)); addrs[space-1] = '\0'; } strncat(addrs, encoded, space+1-1-strlen(addrs)); addrs[space-1] = '\0'; used += (strlen(encoded) + 1); fs_give((void **)&encoded); } } } else if(!strucmp(tag, "note") || !strucmp(tag, "misc")){ if(*value){ escval = vcard_unescape(value); encoded = rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF, (unsigned char *)escval, (altcharset && *altcharset) ? altcharset : (charset && *charset) ? charset : ps_global->posting_charmap); if(encoded){ /* Last one wins */ if(comment) fs_give((void **)&comment); comment = cpystr(encoded); } } } else if(!strucmp(tag, "title")){ if(*value){ escval = vcard_unescape(value); encoded = rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF, (unsigned char *)escval, (altcharset && *altcharset) ? altcharset : (charset && *charset) ? charset : ps_global->posting_charmap); if(encoded){ /* Last one wins */ if(title) fs_give((void **)&title); title = cpystr(encoded); } } } else if(!strucmp(tag, "x-fcc")){ if(*value){ escval = vcard_unescape(value); encoded = rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF, (unsigned char *)escval, (altcharset && *altcharset) ? altcharset : (charset && *charset) ? charset : ps_global->posting_charmap); if(encoded){ /* Last one wins */ if(fcc) fs_give((void **)&fcc); fcc = cpystr(encoded); } } } else if(!strucmp(tag, "fn") || !strucmp(tag, "cn")){ if(*value){ escval = vcard_unescape(value); encoded = rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF, (unsigned char *)escval, (altcharset && *altcharset) ? altcharset : (charset && *charset) ? charset : ps_global->posting_charmap); if(encoded){ /* Last one wins */ if(fullname) fs_give((void **)&fullname); fullname = cpystr(encoded); } } } else if(!strucmp(tag, "n")){ /* * This is a structured name field. It has up to five * fields separated by ';'. The fields are Family Name (which * we'll take to be Last name for our purposes), Given Name * (First name for us), additional names, prefixes, and * suffixes. We'll ignore prefixes and suffixes. * * If we find one of these records we'll use it in preference * to the formatted name field (fn). */ if(*value && *value != ';'){ char *last, *first, *middle = NULL, *rest = NULL; char *esclast, *escfirst, *escmiddle; static char a_semi = ';'; last = value; first = NULL; p = last; while(p && (first = strindex(p, a_semi)) != NULL){ if(first > last && first[-1] != '\\') break; else p = first + 1; } if(first) *first = '\0'; if(first && *(first+1) && *(first+1) != a_semi){ first++; middle = NULL; p = first; while(p && (middle = strindex(p, a_semi)) != NULL){ if(middle > first && middle[-1] != '\\') break; else p = middle + 1; } if(middle) *middle = '\0'; if(middle && *(middle+1) && *(middle+1) != a_semi){ middle++; rest = NULL; p = middle; while(p && (rest = strindex(p, a_semi)) != NULL){ if(rest > middle && rest[-1] != '\\') break; else p = rest + 1; } /* we don't care about the rest */ if(rest) *rest = '\0'; } else middle = NULL; } else first = NULL; /* * Structured name pieces can have multiple values. * We're just going to take the first value in each part. * Skip excaped commas, though. */ p = last; while(p && (comma = strindex(p, a_comma)) != NULL){ if(comma > last && comma[-1] != '\\'){ *comma = '\0'; break; } else p = comma + 1; } p = first; while(p && (comma = strindex(p, a_comma)) != NULL){ if(comma > first && comma[-1] != '\\'){ *comma = '\0'; break; } else p = comma + 1; } p = middle; while(p && (comma = strindex(p, a_comma)) != NULL){ if(comma > middle && comma[-1] != '\\'){ *comma = '\0'; break; } else p = comma + 1; } esclast = vcard_unescape(last); escfirst = vcard_unescape(first); escmiddle = vcard_unescape(middle); snprintf(tmp_20k_buf+10000, SIZEOF_20KBUF-10000, "%s%s%s%s%s", esclast ? esclast : "", escfirst ? ", " : "", escfirst ? escfirst : "", escmiddle ? " " : "", escmiddle ? escmiddle : ""); tmp_20k_buf[SIZEOF_20KBUF-1] = '\0'; encoded = rfc1522_encode(tmp_20k_buf, SIZEOF_20KBUF-10000, (unsigned char *)tmp_20k_buf+10000, (altcharset && *altcharset) ? altcharset : (charset && *charset) ? charset : ps_global->posting_charmap); tmp_20k_buf[SIZEOF_20KBUF-10000-1] = '\0'; if(esclast && *esclast && escfirst && *escfirst){ if(struct_name) fs_give((void **)&struct_name); struct_name = cpystr(encoded); } else{ /* in case we don't get a fullname better than this */ if(!fullname) fullname = cpystr(encoded); } if(esclast) fs_give((void **)&esclast); if(escfirst) fs_give((void **)&escfirst); if(escmiddle) fs_give((void **)&escmiddle); } } /* suggested nickname */ else if(!strucmp(tag, "nickname") || !strucmp(tag, "x-nickname")){ if(*value){ /* Last one wins */ if(nickname) fs_give((void **)&nickname); /* * Nickname can have multiple values. We're just * going to take the first. Skip escaped commas, though. */ p = value; while(p && (comma = strindex(p, a_comma)) != NULL){ if(comma > value && comma[-1] != '\\'){ *comma = '\0'; break; } else p = comma + 1; } nickname = vcard_unescape(value); } } if(tag) fs_give((void **)&tag); if(altcharset) fs_give((void **)&altcharset); if(decoded) fs_give((void **)&decoded); if(escval) fs_give((void **)&escval); } if(title){ if(!comment) comment = title; else fs_give((void **)&title); } if(fullname || struct_name || nickname || fcc || comment || (addrs && *addrs)) selected += vcard_to_ta(addrs, fullname, struct_name, nickname, comment, fcc, ta_list); if(addrs) fs_give((void **)&addrs); free_list_array(&lines); } return(selected); } int cmp_swoop_list(const qsort_t *a1, const qsort_t *a2) { SWOOP_S *x = (SWOOP_S *)a1; SWOOP_S *y = (SWOOP_S *)a2; if(x->dup){ if(y->dup) return((x->dst_enum > y->dst_enum) ? -1 : (x->dst_enum == y->dst_enum) ? 0 : 1); else return(-1); } else if(y->dup) return(1); else return((x > y) ? -1 : (x == y) ? 0 : 1); /* doesn't matter */ } /* * Take the split out contents of a vCard entry and turn them into a TA. * * Always returns 1, the number of TAs added to ta_list. */ int vcard_to_ta(char *addrs, char *fullname, char *better_fullname, char *nickname, char *comment, char *fcc, TA_S **ta_list) { ADDRESS *addrlist = NULL; /* * Parse it into an addrlist, which is what fill_in_ta wants * to see. */ rfc822_parse_adrlist(&addrlist, addrs, fakedomain); if(!addrlist) addrlist = mail_newaddr(); /* empty addr, to make right thing happen */ *ta_list = fill_in_ta(ta_list, addrlist, 1, fullname ? fullname : better_fullname ? better_fullname : "Forwarded Entry"); (*ta_list)->nickname = nickname ? nickname : cpystr(""); (*ta_list)->comment = comment; (*ta_list)->fcc = fcc; /* * We are tempted to want to call switch_to_last_comma_first() here when * we don't have a better_fullname (which is already last, first). * But, since this is the way it was sent to us we should probably leave * it alone. That means that if we use a fullname culled from the * body of a message, or from a header, or from an "N" vcard record, * we will make it be Last, First; but if we use one from an "FN" record * we won't. */ (*ta_list)->fullname = better_fullname ? better_fullname : fullname; if((*ta_list)->nickname) convert_possibly_encoded_str_to_utf8(&(*ta_list)->nickname); if((*ta_list)->comment) convert_possibly_encoded_str_to_utf8(&(*ta_list)->comment); if((*ta_list)->fcc) convert_possibly_encoded_str_to_utf8(&(*ta_list)->fcc); if((*ta_list)->fullname) convert_possibly_encoded_str_to_utf8(&(*ta_list)->fullname); /* * The TA list will free its members but we have to take care of this * extra one that isn't assigned to a TA member. */ if(better_fullname && fullname) fs_give((void **)&fullname); if(addrlist) mail_free_address(&addrlist); return(1); } /* * Look through line which is supposed to look like * * type;charset=iso-8859-1;encoding=b: value * * Type might be email, or nickname, ... It is optional because of the * defaulttype parameter (there isn't such a thing as defaulttype parameters * as of Nov. 96 draft). The semicolon and colon are special chars. Each * parameter in this line is a semicolon followed by the parameter type "=" * the parameter value. Parameters are optional, too. There is always a colon, * followed by value. Whitespace can be everywhere up to where value starts. * There is also an optional "." preceding the type, which we will * ignore. It's for grouping related lines. * (As of July, 97 draft, it is no longer permissible to include a charset * parameter in each line. Only one is permitted in the content-type mime hdr.) * (Also as of July, 97 draft, all white space is supposed to be significant. * We will continue to skip white space surrounding '=' and so forth for * backwards compatibility and because it does no harm in almost all cases.) * (As of Nov, 97 draft, some white space is not significant. That is, it is * allowed in some places.) * * * Args: line -- the line to look at * type -- this is a return value, and is an allocated copy of * the type, freed by the caller. For example, "email". * alt -- this is a return value, and is an allocated copy of * the value of the alternate charset, to be freed by * the caller. For example, this might be "iso-8859-2". * encoded -- this is a return value, and is set to 1 if the value * is b-encoded * * Return value: a pointer to the start of value, or NULL if the syntax * isn't right. It's possible for value to be equal to "". */ char * getaltcharset(char *line, char **type, char **alt, int *encoded) { char *p, *q, *left_semi, *group_dot, *colon; char *start_of_enc, *start_of_cset, tmpsave; static char *cset = "charset"; static char *enc = "encoding"; if(type) *type = NULL; if(alt) *alt = NULL; if(encoded) *encoded = 0; colon = strindex(line, ':'); if(!colon) return NULL; left_semi = strindex(line, ';'); if(left_semi && left_semi > colon) left_semi = NULL; group_dot = strindex(line, '.'); if(group_dot && (group_dot > colon || (left_semi && group_dot > left_semi))) group_dot = NULL; /* * Type is everything up to the semicolon, or the colon if no semicolon. * However, we want to skip optional ".". */ if(type){ q = left_semi ? left_semi : colon; tmpsave = *q; *q = '\0'; *type = cpystr(group_dot ? group_dot+1 : line); *q = tmpsave; sqzspaces(*type); } if(left_semi && alt && (p = srchstr(left_semi+1, cset)) && p < colon){ p += strlen(cset); p = skip_white_space(p); if(*p++ == '='){ p = skip_white_space(p); if(p < colon){ start_of_cset = p; p++; while(p < colon && !isspace((unsigned char)*p) && *p != ';') p++; tmpsave = *p; *p = '\0'; *alt = cpystr(start_of_cset); *p = tmpsave; } } } if(encoded && left_semi && (p = srchstr(left_semi+1, enc)) && p < colon){ p += strlen(enc); p = skip_white_space(p); if(*p++ == '='){ p = skip_white_space(p); if(p < colon){ start_of_enc = p; p++; while(p < colon && !isspace((unsigned char)*p) && *p != ';') p++; if(*start_of_enc == 'b' && start_of_enc + 1 == p) *encoded = 1; } } } p = colon + 1; return(skip_white_space(p)); } /* * Return incoming_fullname in Last, First form. * The passed in fullname should already be in UTF-8. * If there is already a comma, we add quotes around the fullname so we can * tell it isn't a Last, First comma but a real comma in the Fullname. * * Args: incoming_fullname -- * new_full -- passed in pointer to place to put new fullname * nbuf -- size of new_full * Returns: new_full */ void switch_to_last_comma_first(char *incoming_fullname, char *new_full, size_t nbuf) { char save_value; char *save_end, *nf, *inc, *p = NULL; if(nbuf < 1) return; if(incoming_fullname == NULL){ new_full[0] = '\0'; return; } /* get rid of leading white space */ for(inc = incoming_fullname; *inc && isspace((unsigned char)*inc); inc++) ;/* do nothing */ /* get rid of trailing white space */ for(p = inc + strlen(inc) - 1; *p && p >= inc && isspace((unsigned char)*p); p--) ;/* do nothing */ save_end = ++p; if(save_end == inc){ new_full[0] = '\0'; return; } save_value = *save_end; /* so we don't alter incoming_fullname */ *save_end = '\0'; nf = new_full; memset(new_full, 0, nbuf); /* need this the way it is done below */ if(strindex(inc, ',') != NULL){ int add_quotes = 0; /* * Quote already existing comma. * * We'll get this wrong if it is already quoted but the quote * doesn't start right at the beginning. */ if(inc[0] != '"'){ add_quotes++; if(nf-new_full < nbuf-1) *nf++ = '"'; } strncpy(nf, inc, MIN(nbuf - (add_quotes ? 3 : 1), nbuf-(nf-new_full)-1)); new_full[nbuf-1] = '\0'; if(add_quotes){ strncat(nf, "\"", nbuf-(nf-new_full)-1); new_full[nbuf-1] = '\0'; } } else if(strindex(inc, SPACE)){ char *last, *end; end = new_full + nbuf - 1; /* * Switch First Middle Last to Last, First Middle */ /* last points to last word, which does exist */ last = skip_white_space(strrindex(inc, SPACE)); /* copy Last */ for(p = last; *p && nf < end-2 && nf-new_full < nbuf; *nf++ = *p++) ;/* do nothing */ if(nf-new_full < nbuf-1) *nf++ = ','; if(nf-new_full < nbuf-1) *nf++ = SPACE; /* copy First Middle */ for(p = inc; p < last && nf < end && nf-new_full < nbuf-1; *nf++ = *p++) ;/* do nothing */ new_full[nbuf-1] = '\0'; removing_trailing_white_space(new_full); } else strncpy(new_full, inc, nbuf-1); new_full[nbuf-1] = '\0'; *save_end = save_value; } /* * Fetch this body part, content-decode it if necessary, split it into * lines, and return the lines in an allocated array, which is freed * by the caller. Folded lines are replaced by single long lines. */ char ** detach_vcard_att(MAILSTREAM *stream, long int msgno, struct mail_bodystruct *body, char *partnum) { unsigned long length; char *text = NULL, /* raw text */ *dtext = NULL, /* content-decoded text */ **res = NULL, /* array of decoded lines */ *lptr, /* line pointer */ *next_line; int i, count; /* make our own copy of text so we can change it */ text = cpystr(mail_fetchbody_full(stream, msgno, partnum, &length,FT_PEEK)); text[length] = '\0'; /* decode the text */ switch(body->encoding){ default: case ENC7BIT: case ENC8BIT: case ENCBINARY: dtext = text; break; case ENCBASE64: dtext = (char *)rfc822_base64((unsigned char *)text, (unsigned long)strlen(text), &length); if(dtext) dtext[length] = '\0'; else q_status_message(SM_ORDER | SM_DING, 3, 4, "Malformed B64 data in address book attachment."); if(text) fs_give((void **)&text); break; case ENCQUOTEDPRINTABLE: dtext = (char *)rfc822_qprint((unsigned char *)text, (unsigned long)strlen(text), &length); if(dtext) dtext[length] = '\0'; else q_status_message(SM_ORDER | SM_DING, 3, 4, "Malformed QP data in address book attachment."); if(text) fs_give((void **)&text); break; } /* count the lines */ next_line = dtext; count = 0; for(lptr = next_line; lptr && *lptr; lptr = next_line){ for(next_line = lptr; *next_line; next_line++){ /* * Find end of current line. */ if(*next_line == '\r' && *(next_line+1) == '\n'){ next_line += 2; /* not a folded line, count it */ if(!*next_line || (*next_line != SPACE && *next_line != TAB)) break; } } count++; } /* allocate space for resulting array of lines */ res = (char **)fs_get((count + 1) * sizeof(char *)); memset((void *)res, 0, (count + 1) * sizeof(char *)); next_line = dtext; for(i=0, lptr=next_line; lptr && *lptr && i < count; lptr=next_line, i++){ /* * Move next_line to start of next line and null terminate * current line. */ for(next_line = lptr; *next_line; next_line++){ /* * Find end of current line. */ if(*next_line == '\r' && *(next_line+1) == '\n'){ next_line += 2; /* not a folded line, terminate it */ if(!*next_line || (*next_line != SPACE && *next_line != TAB)){ *(next_line-2) = '\0'; break; } } } /* turn folded lines into long lines in place */ vcard_unfold(lptr); res[i] = cpystr(lptr); } if(dtext) fs_give((void **)&dtext); res[count] = NULL; return(res); } /* * Look for possible addresses in the first text part of a message for * use by TakeAddr command. * Returns the number of TA lines added. */ int grab_addrs_from_body(MAILSTREAM *stream, long int msgno, struct mail_bodystruct *body, TA_S **ta_list) { #define MAXLINESZ 2000 char line[MAXLINESZ + 1]; STORE_S *so; gf_io_t pc; SourceType src = CharStar; int added = 0, failure; char *partno = "1"; ATTACH_S *a = ps_global->atmts; dprint((9, "\n - grab_addrs_from_body - \n")); /* * If it is text/plain or it is multipart with a first part of text/plain, * we want to continue, else forget it. */ if(!((body->type == TYPETEXT && body->subtype && ALLOWED_SUBTYPE(body->subtype)) || (body->type == TYPEMULTIPART && body->nested.part && body->nested.part->body.type == TYPETEXT && ALLOWED_SUBTYPE(body->nested.part->body.subtype)))) return 0; #ifdef DOS src = TmpFileStar; #endif if((so = so_get(src, NULL, EDIT_ACCESS)) == NULL) return 0; gf_set_so_writec(&pc, so); if(body->type == TYPEMULTIPART && strucmp(body->subtype, "ALTERNATIVE") == 0 && a && a+1 && (a+1)->shown) partno = "2"; failure = !get_body_part_text(stream, body, msgno, partno, 0L, pc, NULL, NULL, GBPT_NONE); gf_clear_so_writec(so); if(failure){ so_give(&so); return 0; } so_seek(so, 0L, 0); while(get_line_of_message(so, line, sizeof(line))){ ADDRESS *addr; char save_end, *start, *tmp_a_string, *tmp_personal, *p; int n; /* process each @ in the line */ for(p = (char *) line; (start = mail_addr_scan(p, &n)); p = start + n){ tmp_personal = NULL; if(start > line && *(start-1) == '<' && *(start+n) == '>'){ int words, in_quote; char *fn_start; /* * Take a shot at looking for full name * If we can find a colon maybe we've got a header line * embedded in the body. * This is real ad hoc. */ /* * Go back until we run into a character that probably * isn't a fullname character. */ fn_start = start-1; in_quote = words = 0; while(fn_start > p && (in_quote || !(*(fn_start-1) == ':' || *(fn_start-1) == ';' || *(fn_start-1) == ','))){ fn_start--; if(!in_quote && isspace((unsigned char)*fn_start)) words++; else if(*fn_start == '"' && (fn_start == p || *(fn_start-1) != '\\')){ if(in_quote){ in_quote = 0; break; } else in_quote = 1; } if(words == 5) break; } /* wasn't a real quote, forget about fullname */ if(in_quote) fn_start = start-1; /* Skip forward over the white space. */ while(isspace((unsigned char)*fn_start) || *fn_start == '(') fn_start++; /* * Make sure the first word is capitalized. * (just so it is more likely it is a name) */ while(fn_start < start-1 && !(isupper(*fn_start) || *fn_start == '"')){ if(*fn_start == '('){ fn_start++; continue; } /* skip forward over this word */ while(fn_start < start-1 && !isspace((unsigned char)*fn_start)) fn_start++; while(fn_start < start-1 && isspace((unsigned char)*fn_start)) fn_start++; } if(fn_start < start-1){ char *fn_end, save_fn_end; /* remove white space between fullname and start */ fn_end = start-1; while(fn_end > fn_start && isspace((unsigned char)*(fn_end - 1))) fn_end--; save_fn_end = *fn_end; *fn_end = '\0'; /* remove matching quotes */ if(*fn_start == '"' && *(fn_end-1) == '"'){ fn_start++; *fn_end = save_fn_end; fn_end--; save_fn_end = *fn_end; *fn_end = '\0'; } /* copy fullname */ if(*fn_start) tmp_personal = cpystr(fn_start); *fn_end = save_fn_end; } } save_end = *(start+n); *(start+n) = '\0'; /* rfc822_parse_adrlist feels free to destroy input so send copy */ tmp_a_string = cpystr(start); *(start+n) = save_end; addr = NULL; ps_global->c_client_error[0] = '\0'; rfc822_parse_adrlist(&addr, tmp_a_string, fakedomain); if(tmp_a_string) fs_give((void **)&tmp_a_string); if(tmp_personal){ if(addr){ if(addr->personal) fs_give((void **)&addr->personal); addr->personal = tmp_personal; } else fs_give((void **)&tmp_personal); } if((addr && addr->host && addr->host[0] == '@') || ps_global->c_client_error[0]){ mail_free_address(&addr); continue; } if(addr && addr->mailbox && addr->host){ added++; *ta_list = fill_in_ta(ta_list, addr, 0, (char *)NULL); } if(addr) mail_free_address(&addr); } } so_give(&so); return(added); } /* * Get the next line of the object pointed to by source. * Skips empty lines. * Linebuf is a passed in buffer to put the line in. * Linebuf is null terminated and \r and \n chars are removed. * 0 is returned when there is no more input. */ int get_line_of_message(STORE_S *source, char *linebuf, int linebuflen) { int pos = 0; unsigned char c; if(linebuflen < 2) return 0; while(so_readc(&c, source)){ if(c == '\n' || c == '\r'){ if(pos > 0) break; } else{ linebuf[pos++] = c; if(pos >= linebuflen - 2) break; } } linebuf[pos] = '\0'; return(pos); } /* * Inserts a new entry based on addr in the TA list. * Makes sure everything is UTF-8. That is, * Values used for strvalue will be converted to UTF-8 first. * Personal name fields of addr args will be converted to UTF-8. * * Args: old_current -- the TA list * addr -- the address for this line * checked -- start this line out checked * print_string -- if non-null, this line is informational and is just * printed out, it can't be selected */ TA_S * fill_in_ta(TA_S **old_current, struct mail_address *addr, int checked, char *print_string) { TA_S *new_current; /* c-client convention for group syntax, which we don't want to deal with */ if(!print_string && (!addr || !addr->mailbox || !addr->host)) new_current = *old_current; else{ new_current = new_taline(old_current); if(print_string && addr){ /* implied List when here */ new_current->frwrded = 1; new_current->skip_it = 0; new_current->print = 0; new_current->checked = checked != 0; new_current->addr = copyaddrlist(addr); decode_addr_names_to_utf8(new_current->addr); new_current->strvalue = cpystr(print_string); convert_possibly_encoded_str_to_utf8(&new_current->strvalue); } else if(print_string){ new_current->frwrded = 0; new_current->skip_it = 1; new_current->print = 1; new_current->checked = 0; new_current->addr = 0; new_current->strvalue = cpystr(print_string); convert_possibly_encoded_str_to_utf8(&new_current->strvalue); } else{ new_current->frwrded = 0; new_current->skip_it = 0; new_current->print = 0; new_current->checked = checked != 0; new_current->addr = copyaddr(addr); decode_addr_names_to_utf8(new_current->addr); if(new_current->addr && new_current->addr->personal) remove_quotes(new_current->addr->personal); if(addr->host[0] == '.') new_current->strvalue = cpystr("Error in address (ok to try Take anyway)"); else{ char *bufp; size_t len; len = est_size(new_current->addr); bufp = (char *) fs_get(len * sizeof(char)); new_current->strvalue = cpystr(addr_string(new_current->addr, bufp, len)); fs_give((void **) &bufp); } convert_possibly_encoded_str_to_utf8(&new_current->strvalue); } } return(new_current); } int eliminate_dups_and_us(TA_S *list) { return(eliminate_dups_and_maybe_us(list, 1)); } int eliminate_dups_but_not_us(TA_S *list) { return(eliminate_dups_and_maybe_us(list, 0)); } /* * Look for dups in list and mark them so we'll skip them. * * We also throw out any addresses that are us (if us_too is set), since * we won't usually want to take ourselves to the addrbook. * On the otherhand, if there is nothing but us, we leave it. * * Don't toss out forwarded entries. * * Returns the number of dups eliminated that were also selected. */ int eliminate_dups_and_maybe_us(TA_S *list, int us_too) { ADDRESS *a, *b; TA_S *ta, *tb; int eliminated = 0; /* toss dupes */ for(ta = list; ta; ta = ta->next){ if(ta->skip_it || ta->frwrded) /* already tossed or forwarded */ continue; a = ta->addr; /* Check addr "a" versus tail of the list */ for(tb = ta->next; tb; tb = tb->next){ if(tb->skip_it || tb->frwrded) /* already tossed or forwarded */ continue; b = tb->addr; if(dup_addrs(a, b)){ if(ta->checked || !(tb->checked)){ /* throw out b */ if(tb->checked) eliminated++; tb->skip_it = 1; } else{ /* tb->checked */ /* throw out a */ ta->skip_it = 1; break; } } } } if(us_too){ /* check whether all remaining addrs are us */ for(ta = list; ta; ta = ta->next){ if(ta->skip_it) /* already tossed */ continue; if(ta->frwrded) /* forwarded entry, so not us */ break; a = ta->addr; if(!address_is_us(a, ps_global)) break; } /* * if at least one address that isn't us, remove all of us from * the list */ if(ta){ for(ta = list; ta; ta = ta->next){ if(ta->skip_it || ta->frwrded) /* already tossed or forwarded */ continue; a = ta->addr; if(address_is_us(a, ps_global)){ if(ta->checked) eliminated++; /* throw out a */ ta->skip_it = 1; } } } } return(eliminated); } /* * Returns 1 if x and y match, 0 if not. */ int dup_addrs(struct mail_address *x, struct mail_address *y) { return(x && y && strucmp(x->mailbox, y->mailbox) == 0 && strucmp(x->host, y->host) == 0); }