+static int
+csv_is_valid_quote_start(char *p)
+{
+ for(; *p; p++) {
+ if(*p == '\"')
+ return TRUE;
+ else if(!ISSPACE(*p))
+ return FALSE;
+ }
+
+ return FALSE;
+}
+
+static void
+csv_parse_line(char *line, int *table_base, size_t table_size)
+{
+ char *p, *start;
+ int field;
+ bool in_quote = FALSE;
+ list_item item;
+
+ item = item_create();
+
+ for(p = start = line, field = 0; *p; p++) {
+ if(in_quote) {
+ if(csv_is_valid_quote_end(p))
+ in_quote = FALSE;
+ } else {
+ if ( (((p - start) / sizeof (char)) < 2 ) &&
+ csv_is_valid_quote_start(p) )
+ in_quote = TRUE;
+ }
+
+ if(*p == ',' && !in_quote) {
+ *p = 0;
+ csv_store_item(item,
+ csv_field_to_item(table_base,table_size,field),
+ start);
+ field++;
+ start = p + 1;
+ }
+ }
+ /*
+ * store last field
+ */
+ csv_store_item(item, csv_field_to_item(table_base, table_size, field),
+ start);
+
+ csv_convert_emails(item_fget(item, EMAIL));
+ add_item2database(item);
+ item_free(&item);
+}
+
+static int
+csv_parse_file_common(FILE *in, int *conv_table, size_t table_size)
+{
+ char *line = NULL;
+
+ while(!feof(in)) {
+ line = getaline(in);
+
+ if(line && *line && *line != CSV_COMMENT_CHAR)
+ csv_parse_line(line, conv_table, table_size);
+
+ xfree(line);
+ }
+
+ return 0;
+}
+
+static int
+csv_parse_file(FILE *in)
+{
+ return csv_parse_file_common(in, csv_conv_table,
+ CSV_TABLE_SIZE(csv_conv_table));
+}
+
+static int
+allcsv_parse_file(FILE *in)
+{
+ return csv_parse_file_common(in, allcsv_conv_table,
+ CSV_TABLE_SIZE(allcsv_conv_table));
+}
+
+static int
+palmcsv_parse_file(FILE *in)
+{
+ return csv_parse_file_common(in, palmcsv_conv_table,
+ CSV_TABLE_SIZE(palmcsv_conv_table));
+}
+
+/*
+ * end of csv import filter
+ */
+
+/*
+ * vCard import filter
+ */
+
+static char *vcard_fields[] = {
+ "FN", /* FORMATTED NAME */
+ "EMAIL", /* EMAIL */
+ "ADR", /* ADDRESS */
+ "ADR", /* ADDRESS2 */
+ "ADR", /* CITY */
+ "ADR", /* STATE */
+ "ADR", /* ZIP */
+ "ADR", /* COUNTRY */
+ "TEL", /* PHONE */
+ "TEL", /* WORKPHONE */
+ "TEL", /* FAX */
+ "TEL", /* MOBILEPHONE */
+ "NICKNAME", /* NICK */
+ "URL", /* URL */
+ "NOTE", /* NOTES */
+ "BDAY", /* ANNIVERSARY */
+ "N", /* NAME: special case/mapping in vcard_parse_line() */
+ NULL /* ITEM_FIELDS */
+};
+
+enum {
+ VCARD_KEY = 0,
+ VCARD_KEY_ATTRIBUTE,
+ VCARD_VALUE,
+};
+
+static char *
+vcard_get_line_element(char *line, int element)
+{
+ int i;
+ char *line_copy = 0;
+ char *result = 0;
+ char *key = 0;
+ char *key_attr = 0;
+ char *value = 0;
+
+ line_copy = xstrdup(line);
+
+ /* change newline characters, if present, to end of string */
+ for(i=0; line_copy[i]; i++) {
+ if(line_copy[i] == '\r' || line_copy[i] == '\n') {
+ line_copy[i] = '\0';
+ break;
+ }
+ }
+
+ /* separate key from value */
+ for(i=0; line_copy[i]; i++) {
+ if(line_copy[i] == ':') {
+ line_copy[i] = '\0';
+ key = line_copy;
+ value = &line_copy[i+1];
+ break;
+ }
+ }
+
+ /* separate key from key attributes */
+ /* works for vCard 2 as well (automagically) */
+ if (key) {
+ for(i=0; key[i]; i++) {
+ if(key[i] == ';') {
+ key[i] = '\0';
+ key_attr = &key[i+1];
+ break;
+ }
+ }
+ }
+
+ switch(element) {
+ case VCARD_KEY:
+ if(key)
+ result = xstrdup(key);
+ break;
+ case VCARD_KEY_ATTRIBUTE:
+ if(key_attr)
+ result = xstrdup(key_attr);
+ break;
+ case VCARD_VALUE:
+ if(value)
+ result = xstrdup(value);
+ break;
+ }
+
+ xfree(line_copy);
+ return result;
+}
+
+static void
+vcard_parse_email(list_item item, char *line)
+{
+ char *email;
+
+ email = vcard_get_line_element(line, VCARD_VALUE);
+
+ if(item[1]) {
+ item[1] = strconcat(item[1], ",", email, 0);
+ xfree(email);
+ }
+ else {
+ item[1] = email;
+ }
+}
+
+
+/*
+ * mappings between vCard ADR field and abook's ADDRESS
+ * see rfc2426 section 3.2.1
+ */
+static void
+vcard_parse_address(list_item item, char *line)
+{
+ char *value;
+
+ value = vcard_get_line_element(line, VCARD_VALUE);
+ if(!value)
+ return;
+
+ // vCard(post office box) - not used
+ strsep(&value, ";");
+ if(!value) return;
+
+ // vCard(the extended address)
+ item_fput(item, ADDRESS2, xstrdup(strsep(&value, ";")));
+ if(!value) return;
+
+ // vCard(the street address)
+ item_fput(item, ADDRESS, xstrdup(strsep(&value, ";")));
+ if(!value) return;
+
+ // vCard(the locality)
+ item_fput(item, CITY, xstrdup(strsep(&value, ";")));
+ if(!value) return;
+
+ // vCard(the region)
+ item_fput(item, STATE, xstrdup(strsep(&value, ";")));
+ if(!value) return;
+
+ // vCard(the postal code)
+ item_fput(item, ZIP, xstrdup(strsep(&value, ";")));
+ if(!value) return;
+
+ // vCard(the country name)
+ item_fput(item, COUNTRY, xstrdup(strsep(&value, ";")));
+
+ // support of optional trailing ";" to the ADR field
+ if(value && *value) xfree(value);
+}
+
+static void
+vcard_parse_name(list_item item, char *line)
+{
+ // store the "N" field into "NAME" *if* no "FN:"
+ // value has already been stored here
+ if(item[0]) return;
+
+ int i = -1;
+ item[0] = vcard_get_line_element(line, VCARD_VALUE);
+ // "N:" can be multivalued => replace ';' separators by ' '
+ while(item[0][++i]) if(item[0][i] == ';') item[0][i] = ' ';
+
+ // http://www.daniweb.com/software-development/c/code/216919
+ char *original = item[0], *p = original;
+ int trimmed = 0;
+ do {
+ if (*original != ' ' || trimmed) {
+ trimmed = 1; *p++ = *original;
+ }
+ } while(*original++);
+}
+
+static void
+vcard_parse_phone(list_item item, char *line)
+{
+ char *type = vcard_get_line_element(line, VCARD_KEY_ATTRIBUTE);
+ char *value = vcard_get_line_element(line, VCARD_VALUE);
+
+ /* set the standard number */
+ if (!type) item_fput(item, PHONE, value);
+
+ /*
+ * see rfc2426 section 3.3.1
+ * Note: we probably support both vCard 2 and 3
+ */
+ else {
+ if (strcasestr(type, "home") != NULL)
+ item_fput(item, PHONE, xstrdup(value));
+ else if (strcasestr(type, "work") != NULL)
+ item_fput(item, WORKPHONE, xstrdup(value));
+ else if (strcasestr(type, "fax") != NULL)
+ item_fput(item, FAX, xstrdup(value));
+ else if (strcasestr(type, "cell") != NULL)
+ item_fput(item, MOBILEPHONE, xstrdup(value));
+
+ xfree(type);
+ xfree(value);
+ }
+}
+
+static void
+vcard_parse_line(list_item item, char *line)
+{
+ int i;
+ char *key;
+
+ for(i=0; vcard_fields[i]; i++) {
+ key = vcard_fields[i];
+
+ if(0 == strncmp(key, line, strlen(key))) {
+ if(0 == strcmp(key, "EMAIL"))
+ vcard_parse_email(item, line);
+ else if(i == 2)
+ vcard_parse_address(item, line);
+ else if(0 == strcmp(key, "TEL"))
+ vcard_parse_phone(item, line);
+ else if(0 == strcmp(key, "N"))
+ vcard_parse_name(item, line);
+ else
+ item[i] = vcard_get_line_element(line, VCARD_VALUE);
+ return;
+ }
+ }
+}
+
+static void
+vcard_parse_item(FILE *in)
+{
+ char *line = NULL;
+ list_item item = item_create();
+
+ while(!feof(in)) {
+ line = getaline(in);
+
+ if(line && !strncmp("END:VCARD", line, 9)) {
+ xfree(line);
+ break;
+ }
+ else if(line) {
+ vcard_parse_line(item, line);
+ xfree(line);
+ }
+ }
+
+ add_item2database(item);
+ item_free(&item);
+}
+
+static int
+vcard_parse_file(FILE *in)
+{
+ char *line = NULL;
+
+ while(!feof(in)) {
+ line = getaline(in);
+
+ if(line && !strncmp("BEGIN:VCARD", line, 11)) {
+ xfree(line);
+ vcard_parse_item(in);
+ }
+ else if(line) {
+ xfree(line);
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * end of vCard import filter
+ */
+
+/*
+ * csv addressbook export filters
+ */
+
+#define CSV_LAST (-1)
+#define CSV_UNDEFINED (-2)
+#define CSV_SPECIAL(X) (-3 - (X))
+#define CSV_IS_SPECIAL(X) ((X) <= -3)
+
+static int
+csv_export_common(FILE *out, struct db_enumerator e,
+ int fields[], void (*special_func)(FILE *, int, int))
+{
+ int i;
+
+ db_enumerate_items(e) {
+ for(i = 0; fields[i] != CSV_LAST; i++) {
+ if(fields[i] == CSV_UNDEFINED)
+ fprintf(out, "\"\"");
+ else if(CSV_IS_SPECIAL(fields[i])) {
+ if(special_func)
+ (*special_func)(out, e.item, fields[i]);
+ }
+ else if(fields[i] >= CUSTOM_FIELD_START_INDEX) {
+ fprintf(out, "\"%s\"",
+ safe_str(db_fget_byid(e.item, fields[i] - CUSTOM_FIELD_START_INDEX)));
+ }
+ else
+ /*fprintf(out,(
+ strchr(safe_str(database[e.item][field_idx(fields[i])]), ',') ||
+ strchr(safe_str(database[e.item][field_idx(fields[i])]), '\"')) ?
+ "\"%s\"" : "%s",
+ safe_str(database[e.item][field_idx(fields[i])])
+ );*/
+ fprintf(out, "\"%s\"",
+ safe_str(db_fget(e.item,fields[i])));
+
+ if(fields[i + 1] != CSV_LAST)
+ fputc(',', out);
+ }
+ fputc('\n', out);
+ }
+
+ return 0;
+}
+
+static int
+csv_export_database(FILE *out, struct db_enumerator e)
+{
+ int csv_export_fields[] = {
+ NAME,
+ EMAIL,
+ PHONE,
+ NOTES,
+ NICK,
+ CSV_LAST
+ };
+
+ csv_export_common(out, e, csv_export_fields, NULL);
+
+ return 0;
+}
+
+static int
+allcsv_export_database(FILE *out, struct db_enumerator e)
+{
+ /*
+ * TODO: Should get these atomatically from abook_fileds
+ * - JH
+ */
+ int allcsv_export_fields[ITEM_FIELDS + 6] = { // only the 5 custom fields are allowed so far
+ NAME,
+ EMAIL,
+ ADDRESS,
+ ADDRESS2,
+ CITY,
+ STATE,
+ ZIP,
+ COUNTRY,
+ PHONE,
+ WORKPHONE,
+ FAX,
+ MOBILEPHONE, // spelt "mobile" in standard_fields
+ NICK,
+ URL,
+ NOTES,
+ ANNIVERSARY,
+ GROUPS,
+ CSV_LAST
+ };
+
+ fprintf(out, "#");
+ int i = 0;
+ while(allcsv_export_fields[i+1] != CSV_LAST) {
+ fprintf(out, "\"%s\",", standard_fields[i++].key);
+ }
+ fprintf(out, "\"%s\"", standard_fields[i].key);
+
+ /*
+ Custom fields handling:
+ This loop appends custom fields' id at the end of allcsv_export_fields and shift
+ the CSV_LAST sentinel value each time one is found.
+ CUSTOM_FIELD_START_INDEX is added to these index values so csv_export_common()
+ can later recognize them and call db_fget_byid() instead of the traditional db_fget()
+
+ It only search for defined the [legacy?] "custom" fields.
+ */
+
+ // pointer to the end of the field list
+ int append_field = ITEM_FIELDS;
+ // custom field's trailing number (between 1 and 5)
+ int j;
+ // full custom field name, eg "custom4"
+ char custom_field_key[8];
+ // index used by custom_field_key
+ int field_no;
+ // name of the defined field <field_no> as chosen by the user
+ char *custom_field_name;
+
+ for (j = 1; j <= 5; j++) {
+ snprintf(custom_field_key, 8, "custom%d", j++);
+ if(find_declared_field(custom_field_key)) {
+ find_field_number(custom_field_key, &field_no);
+ get_field_info(field_no, NULL, &custom_field_name, NULL);
+ // append the field to the list
+ allcsv_export_fields[append_field] = field_no + CUSTOM_FIELD_START_INDEX;
+ allcsv_export_fields[++append_field] = CSV_LAST;
+ // print column name
+ fprintf(out, ",\"%s\"", custom_field_name);
+ }
+ }
+ free(custom_field_name);
+ fprintf(out, "\n");
+
+ csv_export_common(out, e, allcsv_export_fields, NULL);
+
+ return 0;
+}
+
+/*
+ * palm csv
+ */
+
+#define PALM_CSV_NAME CSV_SPECIAL(0)
+#define PALM_CSV_END CSV_SPECIAL(1)
+#define PALM_CSV_CAT CSV_SPECIAL(2)
+
+static void
+palm_split_and_write_name(FILE *out, char *name)
+{
+ char *p;
+
+ assert(name);
+
+ if ( (p = strchr(name, ' ')) ) {
+ /*
+ * last name first
+ */
+ fprintf(out, "\"%s\",\"" , p + 1);
+ fwrite((void *)name, p - name, sizeof(char), out);
+ fputc('\"', out);
+ } else {
+ fprintf(out, "\"%s\"", safe_str(name));
+ }
+}
+
+static void
+palm_csv_handle_specials(FILE *out, int item, int field)
+{
+ switch(field) {
+ case PALM_CSV_NAME:
+ palm_split_and_write_name(out, db_name_get(item));
+ break;
+ case PALM_CSV_CAT:
+ fprintf(out, "\"abook\"");
+ break;
+ case PALM_CSV_END:
+ fprintf(out, "\"0\"");
+ break;
+ default:
+ assert(0);
+ }
+}
+
+static int
+palm_export_database(FILE *out, struct db_enumerator e)
+{
+ int palm_export_fields[] = {
+ PALM_CSV_NAME, /* LASTNAME, FIRSTNAME */
+ CSV_UNDEFINED, /* TITLE */
+ CSV_UNDEFINED, /* COMPANY */
+ WORKPHONE, /* WORK PHONE */
+ PHONE, /* HOME PHONE */
+ FAX, /* FAX */
+ MOBILEPHONE, /* OTHER */
+ EMAIL, /* EMAIL */
+ ADDRESS, /* ADDRESS */
+ CITY, /* CITY */
+ STATE, /* STATE */
+ ZIP, /* ZIP */
+ COUNTRY, /* COUNTRY */
+ NICK, /* DEFINED 1 */
+ URL, /* DEFINED 2 */
+ CSV_UNDEFINED, /* DEFINED 3 */
+ CSV_UNDEFINED, /* DEFINED 4 */
+ NOTES, /* NOTE */
+ PALM_CSV_END, /* "0" */
+ PALM_CSV_CAT, /* CATEGORY */
+ CSV_LAST
+ };
+
+ csv_export_common(out, e, palm_export_fields, palm_csv_handle_specials);
+
+ return 0;
+}
+
+/*
+ * end of csv export filters
+ */
+
+/*
+ * vCard 2 addressbook export filter
+ */
+
+static int
+vcard_export_database(FILE *out, struct db_enumerator e)
+{
+ db_enumerate_items(e)
+ vcard_export_item(out, e.item);
+ return 0;
+}
+
+void
+vcard_export_item(FILE *out, int item)
+{
+ int j, email_no;
+ char *name, *tmp;
+ abook_list *emails, *em;
+ fprintf(out, "BEGIN:VCARD\r\nFN:%s\r\n",
+ safe_str(db_name_get(item)));
+
+ name = get_surname(db_name_get(item));
+ for( j = strlen(db_name_get(item)) - 1; j >= 0; j-- ) {
+ if((db_name_get(item))[j] == ' ')
+ break;
+ }
+ fprintf(out, "N:%s;%.*s\r\n",
+ safe_str(name),
+ j,
+ safe_str(db_name_get(item))
+ );
+
+ free(name);
+
+ if(db_fget(item, NICK))
+ fprintf(out, "NICKNAME:%s\r\n",
+ safe_str(db_fget(item, NICK)));
+ if(db_fget(item, ANNIVERSARY))
+ fprintf(out, "BDAY:%s\r\n",
+ safe_str(db_fget(item, ANNIVERSARY)));
+
+ // see rfc6350 section 6.3.1
+ if(db_fget(item, ADDRESS)) {
+ fprintf(out, "ADR:;%s;%s;%s;%s;%s;%s\r\n",
+ // pobox (unsupported)
+ safe_str(db_fget(item, ADDRESS2)), // ext (n°, ...)
+ safe_str(db_fget(item, ADDRESS)), // street
+ safe_str(db_fget(item, CITY)), // locality
+ safe_str(db_fget(item, STATE)), // region
+ safe_str(db_fget(item, ZIP)), // code (postal)
+ safe_str(db_fget(item, COUNTRY)) // country
+ );
+ }
+
+ if(db_fget(item, PHONE))
+ fprintf(out, "TEL;HOME:%s\r\n",
+ db_fget(item, PHONE));
+ if(db_fget(item, WORKPHONE))
+ fprintf(out, "TEL;WORK:%s\r\n",
+ db_fget(item, WORKPHONE));
+ if(db_fget(item, FAX))
+ fprintf(out, "TEL;FAX:%s\r\n",
+ db_fget(item, FAX));
+ if(db_fget(item, MOBILEPHONE))
+ fprintf(out, "TEL;CELL:%s\r\n",
+ db_fget(item, MOBILEPHONE));
+
+ tmp = db_email_get(item);
+ if(*tmp) {
+ emails = csv_to_abook_list(tmp);
+ fprintf(out, "EMAIL;PREF;INTERNET:%s\r\n", emails->data);
+ email_no = 1;
+ for(em = emails->next; em; em = em->next, email_no++ )
+ fprintf(out, "EMAIL;%d;INTERNET:%s\r\n", email_no, em->data);
+
+ abook_list_free(&emails);
+ }
+ free(tmp);
+
+ if(db_fget(item, NOTES))
+ fprintf(out, "NOTE:%s\r\n",
+ db_fget(item, NOTES));
+ if(db_fget(item, URL))
+ fprintf(out, "URL:%s\r\n",
+ db_fget(item, URL));
+
+ fprintf(out, "END:VCARD\r\n\r\n");
+
+}
+
+/*
+ * end of vCard export filter
+ */
+
+
+/*
+ * mutt alias export filter
+ */
+
+static char *
+mutt_alias_genalias(int i)
+{
+ char *tmp, *pos;
+
+ if(db_fget(i, NICK))
+ return xstrdup(db_fget(i, NICK));
+
+ tmp = xstrdup(db_name_get(i));
+
+ if( ( pos = strchr(tmp, ' ') ) )
+ *pos = 0;
+
+ strlower(tmp);
+
+ return tmp;
+}
+
+/*
+ * This function is a variant of abook_list_to_csv
+ * */
+static char *
+mutt_alias_gengroups(int i)
+{
+ char *groups, *res = NULL;
+ char groupstr[7] = "-group ";
+ abook_list *list, *tmp;
+
+ groups = db_fget(i, GROUPS);
+
+ if(!groups)
+ return NULL;
+
+ list = csv_to_abook_list(groups);
+ for(tmp = list; tmp; tmp = tmp->next) {
+ if(tmp == list) {
+ res = xmalloc(strlen(groupstr)+strlen(tmp->data)+1);
+ res = strcpy(res, groupstr);
+ } else {
+ res = xrealloc(res, strlen(res)+1+strlen(groupstr)+strlen(tmp->data)+1);
+ strcat(res, " ");
+ strcat(res, groupstr);
+ }
+ strcat(res, tmp->data);
+ }
+ abook_list_free(&list);
+ xfree(groups);
+
+ return res;
+}
+
+static int
+mutt_alias_export(FILE *out, struct db_enumerator e)
+{
+ char email[MAX_EMAIL_LEN];
+ char *alias = NULL;
+ char *groups = NULL;
+ int email_addresses;
+ char *ptr;
+
+ db_enumerate_items(e) {
+ alias = (field_id(NICK) != -1) ? mutt_alias_genalias(e.item) : NULL;
+ groups = (field_id(GROUPS) != -1) ? mutt_alias_gengroups(e.item) : NULL;
+ get_first_email(email, e.item);
+
+ /* do not output contacts without email address */
+ /* cause this does not make sense in mutt aliases */
+ if (*email) {
+
+ /* output first email address */
+ fprintf(out,"alias ");
+ if(groups)
+ fprintf(out, "%s ", groups);
+ if(alias)
+ fprintf(out, "%s ", alias);
+ fprintf(out, "%s <%s>\n",
+ db_name_get(e.item),
+ email);
+
+ /* number of email addresses */
+ email_addresses = 1;
+ ptr = db_email_get(e.item);
+ while (*ptr != '\0') {
+ if (*ptr == ',') {
+ email_addresses++;
+ }
+ ptr++;
+ }
+
+ /* output other email addresses */
+ while (email_addresses-- > 1) {
+ roll_emails(e.item, ROTATE_RIGHT);
+ get_first_email(email, e.item);
+ fprintf(out,"alias ");
+ if( groups )
+ fprintf(out, "%s ", groups);
+ if(alias)
+ fprintf(out, "%s__%s ", alias, email);
+ else
+ fprintf(out, "%s__%s ", db_name_get(e.item), email);
+ fprintf(out, "%s <%s>\n",
+ db_name_get(e.item),
+ email);
+ }
+ roll_emails(e.item, ROTATE_RIGHT);
+ xfree(alias);
+ xfree(groups);
+ }
+ }
+
+ return 0;
+}
+
+void muttq_print_item(FILE *file, int item)
+{
+ abook_list *emails, *e;
+ char *tmp = db_email_get(item);
+
+ emails = csv_to_abook_list(tmp);
+ free(tmp);
+
+ for(e = emails; e; e = e->next) {
+ fprintf(file, "%s\t%s\t%s\n", e->data, db_name_get(item),
+ !db_fget(item, NOTES) ?" " :db_fget(item, NOTES)
+ );
+ if(!opt_get_bool(BOOL_MUTT_RETURN_ALL_EMAILS))
+ break;
+ }
+ abook_list_free(&emails);
+}
+
+static int
+mutt_query_export_database(FILE *out, struct db_enumerator e)
+{
+ fprintf(out, "All items\n");
+ db_enumerate_items(e)
+ muttq_print_item(out, e.item);
+ return 0;
+}
+
+/*