#include "xmalloc.h"
#include <assert.h>
+#ifdef HAVE_VFORMAT
+#include "vcard.h"
+#endif
+
extern abook_field_list *fields_list;
extern int fields_count;
+// see also enum field_types @database.h
+extern abook_field standard_fields[];
/*
* function declarations
*/
void vcard_export_item(FILE *out, int item);
-
+void muttq_print_item(FILE *file, int item);
+void custom_print_item(FILE *out, int item);
/*
* end of function declarations
{
int i;
- puts(_("input:"));
+ puts(_("input formats:"));
for(i=0; *i_filters[i].filtname ; i++)
printf("\t%s\t%s\n", i_filters[i].filtname,
gettext(i_filters[i].desc));
putchar('\n');
- puts(_("output:"));
+ puts(_("output formats:"));
for(i=0; *e_filters[i].filtname ; i++)
printf("\t%s\t%s\n", e_filters[i].filtname,
gettext(e_filters[i].desc));
putchar('\n');
- puts(_("output (with query):"));
+ puts(_("query-compatible output formats:"));
for(i=0; *u_filters[i].filtname ; i++)
printf("\t%s\t%s\n", u_filters[i].filtname,
gettext(u_filters[i].desc));
if(i < 0)
return -1;
+#ifdef HAVE_VFORMAT
+ // this is a special case for
+ // libvformat whose API expects a filename
+ if(!strcmp(filtname, "vcard")) {
+ if(!strcmp(filename, "-"))
+ ret = vcard_parse_file_libvformat("/dev/stdin");
+ else
+ ret = vcard_parse_file_libvformat(filename);
+ }
+ else
+#endif
+
if(!strcmp(filename, "-")) {
struct stat s;
if((fstat(fileno(stdin), &s)) == -1 || S_ISDIR(s.st_mode))
#include "ldif.h"
-static void ldif_fix_string(char *str);
+/* During LDIF import we need more fields than the
+ ITEM_FIELDS of a *list_item. Eg: "objectclass"
+ to test valid records, ...
+ Here we extends the existing field_types enum
+ to define new fields indexes usable during processing.
+ Newly created LDIF attr names could be associated to
+ them using ldif_conv_table[]. */
+typedef enum {
+ LDIF_OBJECTCLASS = ITEM_FIELDS + 1
+} ldif_field_types;
-#define LDIF_ITEM_FIELDS 16
+#define LDIF_ITEM_FIELDS LDIF_OBJECTCLASS
typedef char *ldif_item[LDIF_ITEM_FIELDS];
+/* LDIF field's names *must* respect the ordering
+ defined by the field_types enum from database.h
+ This is only used to define (for export only)
+ abook standard field to LDIF attr name mappings */
static ldif_item ldif_field_names = {
- "cn",
- "mail",
- "streetaddress",
- "streetaddress2",
- "locality",
- "st",
- "postalcode",
- "countryname",
- "homephone",
- "description",
- "homeurl",
- "facsimiletelephonenumber",
- "cellphone",
- "xmozillaanyphone",
- "xmozillanickname",
- "objectclass", /* this must be the last entry */
+ "cn", // NAME
+ "mail", // EMAIL
+ "streetaddress", // ADDRESS
+ "streetaddress2", // ADDRESS2
+ "locality", // CITY
+ "st", // STATE
+ "postalcode", // ZIP
+ "countryname", // COUNTRY
+ "homephone", // PHONE
+ "telephonenumber", // WORKPHONE
+ "facsimiletelephonenumber", // FAX
+ "cellphone", // MOBILEPHONE
+ "xmozillanickname", // NICK
+ "homeurl", // URL
+ "description", // NOTES
+ "anniversary", // ANNIVERSARY
+ "ou", // GROUPS
};
-static int ldif_conv_table[LDIF_ITEM_FIELDS] = {
- NAME, /* "cn" */
- EMAIL, /* "mail" */
- ADDRESS, /* "streetaddress" */
- ADDRESS2, /* "streetaddress2" */
- CITY, /* "locality" */
- STATE, /* "st" */
- ZIP, /* "postalcode" */
- COUNTRY, /* "countryname" */
- PHONE, /* "homephone" */
- NOTES, /* "description" */
- URL, /* "homeurl" */
- FAX, /* "facsimiletelephonenumber" */
- MOBILEPHONE, /* "cellphone" */
- WORKPHONE, /* "xmozillaanyphone" */
- NICK, /* "xmozillanickname" */
- -1, /* "objectclass" */ /* this must be the last entry */
+/* Used to map LDIF attr names from input to
+ the abook restricted set of standard fields. */
+typedef struct {
+ char *key;
+ int index;
+} ldif_available_items;
+
+static ldif_available_items ldif_conv_table[] = {
+ /* initial field names respect the field_types enum
+ from database.h but this is only for readability.
+ This ldif_item struct allow use to define multiple
+ LDIF field names ("attribute names") for one abook field */
+
+ {"cn", NAME}, // 0
+ {"mail", EMAIL},
+ {"streetaddress", ADDRESS},
+ {"streetaddress2", ADDRESS2},
+ {"locality", CITY},
+ {"st", STATE},
+ {"postalcode", ZIP},
+ {"countryname", COUNTRY},
+ {"homephone", PHONE},
+ {"telephonenumber", WORKPHONE}, // workphone, according to Mozilla
+ {"facsimiletelephonenumber", FAX},
+ {"cellphone", MOBILEPHONE},
+ {"mozillanickname", NICK},
+ {"homeurl", URL},
+ {"description", NOTES},
+ {"anniversary", ANNIVERSARY},
+ {"ou", GROUPS}, // 16
+
+ // here comes a couple of aliases
+ {"mozillasecondemail", EMAIL},
+ {"homecity", CITY},
+ {"zip", ZIP},
+ {"tel", PHONE},
+ {"xmozillaanyphone", WORKPHONE}, // ever used ?
+ {"workphone", WORKPHONE},
+ {"fax", FAX},
+ {"telexnumber", FAX},
+ {"mobilephone", MOBILEPHONE},
+ {"mobile", MOBILEPHONE},
+ {"xmozillanickname", NICK},
+ {"labeledURI", URL},
+ {"notes", NOTES},
+ {"birthday", ANNIVERSARY},
+ {"category", GROUPS},
+
+ /* TODO:
+ "sn": append to lastname ?
+ "surname": append to lastname ?
+ "givenname": prepend to firstname ? */
+
+ /* here starts dummy fields:
+
+ As long as additional indexes are created
+ (using the above ldif_field_types),
+ any other LDIF attr name can be added and
+ used during ldif entry processing.
+ But obviously fields > ITEM_FIELDS (database.h) won't be
+ copied into the final *list_item. */
+
+ /* - to avoid mistake, don't use the special ITEM_FIELDS value.
+ - see also: http://mxr.mozilla.org/comm-central/source/mailnews/addrbook/src/nsAbLDIFService.cpp */
+
+ // used to check valid LDIF records:
+ {"objectclass", LDIF_OBJECTCLASS}
};
+const int LDIF_IMPORTABLE_ITEM_FIELDS = (int)sizeof(ldif_conv_table)/sizeof(*ldif_conv_table);
-
+/*
+ Handles multi-line strings.
+ If a string starts with a space, it's the continuation
+ of the previous line. Thus we need to always read ahead.
+ But for this to work with stdin, we need to stores the next
+ line for later use in case it's not a continuation of the
+ first line.
+ */
static char *
-ldif_read_line(FILE *in)
+ldif_read_line(FILE *in, char **next_line)
{
char *buf = NULL;
char *ptr, *tmp;
- long pos;
- int i;
+ char *line;
- for(i = 1;;i++) {
- char *line;
+ // buf filled with the first line
+ if(!*next_line)
+ buf = getaline(in);
+ else {
+ buf = xstrdup(*next_line);
+ xfree(*next_line);
+ }
- pos = ftell(in);
+ while(!feof(in)) {
+ // if no line already read-ahead.
line = getaline(in);
+ if(!line) break;
- if(feof(in) || !line)
- break;
-
- if(i == 1) {
- buf = line;
- continue;
- }
-
+ // this is not a continuation of what is already in buf
+ // store it for the next round
if(*line != ' ') {
- fseek(in, pos, SEEK_SET); /* fixme ! */
- free(line);
+ *next_line = line;
break;
}
+ // starts with ' ': this is the continuation of buf
ptr = line;
while( *ptr == ' ')
ptr++;
list_item item;
int i;
- item = item_create();
-
- if(!li[LDIF_ITEM_FIELDS -1])
+ /* if there's no value for "objectclass"
+ it's probably a buggy record */
+ if(!li[LDIF_OBJECTCLASS])
goto bail_out;
-
- for(i=0; i < LDIF_ITEM_FIELDS; i++) {
- if(ldif_conv_table[i] >= 0 && li[i] && *li[i])
- item_fput(item,ldif_conv_table[i],xstrdup(li[i]));
+ /* just copy from our extended ldif_item to a regular
+ list_item,
+ TODO: API could be changed so db_fput_byid() is usable */
+ item = item_create();
+ for(i=0; i < ITEM_FIELDS; i++) {
+ if(li[i] && *li[i])
+ item[i] = xstrdup(li[i]);
}
add_item2database(item);
+ item_free(&item);
bail_out:
for(i=0; i < LDIF_ITEM_FIELDS; i++)
xfree(li[i]);
- item_free(&item);
-
}
static void
ldif_convert(ldif_item item, char *type, char *value)
{
- int i;
-
+ /* this is the first (mandatory) attribute to expected
+ from a new valid LDIF record.
+ The previous record must be added to the database before
+ we can go further with the new one */
if(!strcmp(type, "dn")) {
ldif_add_item(item);
return;
}
- for(i=0; i < LDIF_ITEM_FIELDS; i++) {
- if(!safe_strcmp(ldif_field_names[i], type) && *value) {
- if(i == LDIF_ITEM_FIELDS - 1) /* this is a dirty hack */
- if(safe_strcmp("person", value))
- break;
+ int i, index;
+
+ for( i=0; i < LDIF_IMPORTABLE_ITEM_FIELDS; i++ ) {
+
+ if( *value && // there's a value for the attr provided
+ ldif_conv_table[i].key && // there exists an ldif attr name...
+ !strcasecmp(ldif_conv_table[i].key, type)) { // ...matching that provided at input
+
+ assert((i >= 0) && (i < LDIF_ITEM_FIELDS));
+ // which abook field this attribute's name maps to ?
+ index = ldif_conv_table[i].index;
+ assert((index >= 0) && (index < LDIF_ITEM_FIELDS));
+
+ /* TODO: here must be handled multi-valued cases
+ (first or latest win, append or prepend values, ...)
+ Currently: emails are appended, for other fields the
+ first attribute found wins.
+ Eg: the value of "mobile" will be taken into
+ account if such a line comes before "cellphone". */
+
+ /* Remember: LDIF_ITEM_FIELDS > ITEM_FIELDS,
+ lower (common) indexes of *ldif_item map well to *list_item.
+ We can use item_fput()... */
+ if(index < ITEM_FIELDS) {
+ // multiple email support, but two only will stay
+ // in the final *list_item
+ if(index == EMAIL && item_fget(item, EMAIL)) {
+ item_fput(item,
+ EMAIL,
+ strconcat(item_fget(item, EMAIL), ",", value, 0));
+ }
+ else {
+ /* Don't override already initialized fields:
+ This is the rule of the "first win" */
+ if(! item_fget(item, index))
+ item_fput(item, index, xstrdup(value));
+ }
+ }
- if(item_fget(item, i))
- free(item_fget(item, i));
+ /* ... but if the ldif field's name index is higher
+ than what standards abook fields struct can hold,
+ these extra indexes must be managed manually.
+ This is the case of LDIF_OBJECTCLASS ("objectclass" attr) */
+ else {
+ item[index] = xstrdup(value);
+ }
- item_fput(item, i, xstrdup(value));
+ // matching attr found and field filled:
+ // no further attr search is needed for `type`
+ break;
}
}
}
ldif_parse_file(FILE *handle)
{
char *line = NULL;
+ char *next_line = NULL;
char *type, *value;
int vlen;
+
+ /* This is our extended fields holder to put the values from
+ successfully parsed LDIF attributes.
+ ldif_item item is temporary. When the end of an entry is reached,
+ values are copied into a regular *list_item struct, see ldif_add_item() */
ldif_item item;
memset(item, 0, sizeof(item));
do {
- if( !(line = ldif_read_line(handle)) )
- continue;
+ line = ldif_read_line(handle, &next_line);
+
+ // EOF or empty lines: continue;
+ if(!line || *line == '\0') continue;
if(-1 == (str_parse_line(line, &type, &value, &vlen))) {
xfree(line);
continue; /* just skip the errors */
}
- ldif_fix_string(value);
-
ldif_convert(item, type, value);
xfree(line);
} while ( !feof(handle) );
+ // force registration (= ldif_add_item()) of the last LDIF entry
ldif_convert(item, "dn", "");
return 0;
}
-static void
-ldif_fix_string(char *str)
-{
- int i, j;
-
- for(i = 0, j = 0; j < (int)strlen(str); i++, j++)
- str[i] = ( str[j] == (char)0xc3 ?
- (char) str[++j] + (char) 0x40 :
- str[j] );
-
- str[i] = 0;
-}
-
/*
* end of ldif import
*/
ldif_export_database(FILE *out, struct db_enumerator e)
{
char email[MAX_EMAILSTR_LEN];
+ abook_list *emails, *em;
fprintf(out, "version: 1\n");
int j;
get_first_email(email, e.item);
- tmp = strdup_printf("cn=%s,mail=%s",db_name_get(e.item),email);
+ if(*email)
+ tmp = strdup_printf("cn=%s,mail=%s",db_name_get(e.item),email);
+ /* TODO: this may not be enough for a trully "Distinguished" name
+ needed by LDAP. Appending a random uuid could do the trick */
+ else
+ tmp = strdup_printf("cn=%s",db_name_get(e.item));
ldif_fput_type_and_value(out, "dn", tmp);
free(tmp);
- for(j = 0; j < LDIF_ITEM_FIELDS; j++) {
- if(ldif_conv_table[j] >= 0) {
- if(ldif_conv_table[j] == EMAIL)
- ldif_fput_type_and_value(out,
- ldif_field_names[j], email);
- else if(db_fget(e.item,ldif_conv_table[j]))
- ldif_fput_type_and_value(out,
- ldif_field_names[j],
- db_fget(e.item,
- ldif_conv_table[j]));
+ for(j = 0; j < ITEM_FIELDS; j++) {
+ if(j == EMAIL) {
+ if(*email) {
+ tmp = db_email_get(e.item);
+ emails = csv_to_abook_list(tmp);
+ free(tmp);
+ for(em = emails; em; em = em->next)
+ ldif_fput_type_and_value(out,
+ ldif_field_names[EMAIL],
+ em->data);
+ }
+ }
+ else if(db_fget(e.item,j)) {
+ ldif_fput_type_and_value(out,
+ ldif_field_names[j],
+ db_fget(e.item, j));
}
}
"FN", /* FORMATTED NAME */
"EMAIL", /* EMAIL */
"ADR", /* ADDRESS */
- "ADR", /* ADDRESS2 - not used */
+ "ADR", /* ADDRESS2 */
"ADR", /* CITY */
"ADR", /* STATE */
"ADR", /* ZIP */
NULL /* not implemented: ANNIVERSARY, ITEM_FIELDS */
};
-/*
- * mappings between vCard ADR field and abook's ADDRESS
- * see rfc2426 section 3.2.1
- */
-static int vcard_address_fields[] = {
- -1, /* vCard(post office box) - not used */
- -1, /* vCard(the extended address) - not used */
- 2, /* vCard(the street address) - ADDRESS */
- 4, /* vCard(the locality) - CITY */
- 5, /* vCard(the region) - STATE */
- 6, /* vCard(the postal code) - ZIP */
- 7 /* vCard(the country name) - COUNTRY */
-};
-
enum {
VCARD_KEY = 0,
VCARD_KEY_ATTRIBUTE,
}
}
+
+/*
+ * 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)
{
- int i;
- int k;
char *value;
- char *address_field;
value = vcard_get_line_element(line, VCARD_VALUE);
if(!value)
return;
- address_field = value;
- for(i=k=0; value[i]; i++) {
- if(value[i] == ';') {
- value[i] = '\0';
- if(vcard_address_fields[k] >= 0) {
- item[vcard_address_fields[k]] = xstrdup(address_field);
- }
- address_field = &value[i+1];
- k++;
- if((k+1)==(sizeof(vcard_address_fields)/sizeof(*vcard_address_fields)))
- break;
- }
- }
- item[vcard_address_fields[k]] = xstrdup(address_field);
- xfree(value);
+ // vCard(post office box) - not used
+ strsep(&value, ";");
+ // vCard(the extended address)
+ item_fput(item, ADDRESS2, xstrdup(strsep(&value, ";")));
+ // vCard(the street address)
+ item_fput(item, ADDRESS, xstrdup(strsep(&value, ";")));
+ // vCard(the locality)
+ item_fput(item, CITY, xstrdup(strsep(&value, ";")));
+ // vCard(the region)
+ item_fput(item, STATE, xstrdup(strsep(&value, ";")));
+ // vCard(the postal code)
+ item_fput(item, ZIP, xstrdup(strsep(&value, ";")));
+ // vCard(the country name)
+ item_fput(item, COUNTRY, xstrdup(strsep(&value, ";")));
+
+ if(*value) xfree(value);
}
static void
PHONE,
WORKPHONE,
FAX,
- MOBILEPHONE,
+ MOBILEPHONE, // spelled "mobile" in standard_fields
NICK,
URL,
NOTES,
};
fprintf(out, "#");
- fprintf(out, "\"NAME\",");
- fprintf(out, "\"EMAIL\",");
- fprintf(out, "\"ADDRESS\",");
- fprintf(out, "\"ADDRESS2\",");
- fprintf(out, "\"CITY\",");
- fprintf(out, "\"STATE\",");
- fprintf(out, "\"ZIP\",");
- fprintf(out, "\"COUNTRY\",");
- fprintf(out, "\"PHONE\",");
- fprintf(out, "\"WORKPHONE\",");
- fprintf(out, "\"FAX\",");
- fprintf(out, "\"MOBILEPHONE\",");
- fprintf(out, "\"NICK\",");
- fprintf(out, "\"URL\",");
- fprintf(out, "\"NOTES\",");
- fprintf(out, "\"ANNIVERSARY\",");
- fprintf(out, "\"GROUPS\"\n");
+ int i = 0;
+ while(allcsv_export_fields[i+1] != CSV_LAST) {
+ fprintf(out, "\"%s\",", standard_fields[i++].key);
+ }
+ fprintf(out, "\"%s\"\n", standard_fields[i].key);
csv_export_common(out, e, allcsv_export_fields, NULL);
void
vcard_export_item(FILE *out, int item)
{
- int j;
+ int j, email_no;
char *name, *tmp;
abook_list *emails, *em;
fprintf(out, "BEGIN:VCARD\r\nFN:%s\r\n",
free(name);
- if(db_fget(item, ADDRESS))
- fprintf(out, "ADR:;;%s;%s;%s;%s;%s;%s\r\n",
- safe_str(db_fget(item, ADDRESS)),
- safe_str(db_fget(item, ADDRESS2)),
- safe_str(db_fget(item, CITY)),
- safe_str(db_fget(item, STATE)),
- safe_str(db_fget(item, ZIP)),
- safe_str(db_fget(item, COUNTRY))
- );
+ // 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",
tmp = db_email_get(item);
if(*tmp) {
emails = csv_to_abook_list(tmp);
-
- for(em = emails; em; em = em->next)
- fprintf(out, "EMAIL;INTERNET:%s\r\n", em->data);
+ 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);
}
return 0;
}
-// see enum field_types @database.h
-static char *conv_table[] = {
- "name",
- "email",
- "address",
- "address2",
- "city",
- "state",
- "zip",
- "country",
- "phone",
- "workphone",
- "fax",
- "mobile",
- "nick",
- "url",
- "notes",
- "anniversary",
- 0 /* ITEM_FIELDS */
-};
-
static int
find_field_enum(char *s) {
- int i = 0;
- while (conv_table[i]) {
- if(!safe_strcmp(conv_table[i], s))
- return i;
- i++;
- }
- // failed
- return -1;
+ int i = -1;
+ while(standard_fields[++i].key)
+ if(!strcmp(standard_fields[i].key, s))
+ return i;
+ return -1;
}
/* Convert a string with named placeholders to
exit(EXIT_FAILURE);
}
+ char tmp[1] = { 0 };
char *p, *start, *field_name = NULL;
p = start = s;
while(*p) {
if(*p == '{') {
start = ++p;
+
+ if(! *start) goto cannotparse;
p = strchr(start, '}');
- if(! *p) {
- fprintf(stderr, _("parse_custom_format: invalid format\n"));
- exit(EXIT_FAILURE);
- }
+ if(! p) goto cannotparse;
strcat(fmt_string, "%s");
field_name = strndup(start, (size_t)(p-start));
*ft = find_field_enum(field_name);
}
ft++;
- p++;
- start = p;
- } else {
+ start = ++p;
+ }
+
+ else if(*p == '\\') {
+ ++p;
+ if(! *p) tmp[0] = '\\'; // last char is a '\' ?
+ else if(*p == 'n') *tmp = '\n';
+ else if(*p == 't') *tmp = '\t';
+ else if(*p == 'r') *tmp = '\r';
+ else if(*p == 'v') *tmp = '\v';
+ else if(*p == 'b') *tmp = '\b';
+ else if(*p == 'a') *tmp = '\a';
+ else *tmp = *p;
+ strncat(fmt_string, tmp, 1);
+ start = ++p;
+ }
+
+ // if no '\' following: quick mode using strchr/strncat
+ else if(! strchr(start, '\\')) {
p = strchr(start, '{');
- if(p && *p) {
+ if(p) { // copy until the next placeholder
strncat(fmt_string, start, (size_t)(p-start));
start = p;
}
- else {
+ else { // copy till the end
strncat( fmt_string,
start,
FORMAT_STRING_LEN - strlen(fmt_string) - 1 );
break;
}
}
+
+ // otherwise character by character
+ else {
+ strncat(fmt_string, p, 1);
+ start = ++p;
+ }
}
- *ft = 66;
+
+ *ft = ITEM_FIELDS;
+ return;
+
+ cannotparse:
+ fprintf(stderr, _("%s: invalid format, index %ld\n"), __FUNCTION__, (start - s));
+ free(fmt_string);
+ while(*ft) free(ft--);
+ exit(EXIT_FAILURE);
}
static int
// we first check that all fields exist before continuing
if(*fmt == '!') {
enum field_types *ftp = ft;
- while(*ft != 66) {
+ while(*ft != ITEM_FIELDS) {
if(! db_fget(item, *ft) )
return 1;
ft++;
fprintf(out, "%s", safe_str(db_fget(item, *ft)));
ft++;
fmt+=2;
- } else if (*ft == 66) {
+ } else if (*ft == ITEM_FIELDS) {
fprintf(out, "%s", fmt);
return 0;
} else {