/* See LICENSE file for copyright and license details. */
#include "common.h"
ATTRIBUTE_PURE
static time_t
gettime(const char *data)
{
time_t ret = 0;
while (*data == ' ' || *data == '\t') data++;
data = &strchr(data, ' ')[1];
if ('1' > *data || *data > '9')
return 0;
for (; isdigit(*data); data++) {
if (ret > (TIME_MAX - (*data & 15)) / 10)
return 0;
ret = ret * 10 + (*data & 15);
}
if (*data)
return 0;
return ret;
}
ATTRIBUTE_PURE
static unsigned int
getposuint(const char *data)
{
unsigned int ret = 0;
while (*data == ' ' || *data == '\t') data++;
data = &strchr(data, ' ')[1];
if ('1' > *data || *data > '9')
return 0;
for (; isdigit(*data); data++) {
if (ret > (UINT_MAX - (*data & 15)) / 10)
return 0;
ret = ret * 10 + (*data & 15);
}
if (*data)
return 0;
return ret;
}
ATTRIBUTE_PURE
static unsigned char
getposuchar(const char *data)
{
unsigned char ret = 0;
while (*data == ' ' || *data == '\t') data++;
data = &strchr(data, ' ')[1];
if ('1' > *data || *data > '9')
return 0;
for (; isdigit(*data); data++) {
if (ret > (UCHAR_MAX - (*data & 15)) / 10)
return 0;
ret = (unsigned char)(ret * 10 + (*data & 15));
}
if (*data)
return 0;
return ret;
}
ATTRIBUTE_PURE
static char *
getstr(char *data)
{
while (*data == ' ' || *data == '\t') data++;
return &strchr(data, ' ')[1];
}
ATTRIBUTE_PURE
static char *
unindent(char *data)
{
while (*data == ' ' || *data == '\t') data++;
return data;
}
static int
addstr(char ***listp, const char *new)
{
size_t i;
void *temp, *add;
int error;
add = strdup(new);
if (!add)
return -1;
if (!*listp) {
*listp = calloc(2, sizeof(char *));
if (!*listp)
goto fail_errno;
(*listp)[0] = add;
} else {
for (i = 0; (*listp)[i]; i++);
if (i > SIZE_MAX / sizeof(char *) + 2) {
error = ENOMEM;
goto fail;
}
temp = realloc(*listp, (i + 2) * sizeof(char *));
if (!temp)
goto fail_errno;
*listp = temp;
(*listp)[i + 0] = add;
(*listp)[i + 1] = NULL;
}
return 0;
fail_errno:
error = errno;
fail:
free(add);
errno = error;
return -1;
}
static int
appendstr(char **strp, char *new)
{
size_t newlen = strlen(new) + 2;
size_t oldlen = 0;
char *p;
if (*strp)
oldlen = strlen(*strp);
newlen += oldlen;
p = realloc(*strp, newlen);
if (!p)
return -1;
*strp = p;
stpcpy(stpcpy(&p[oldlen], new), "\n");
return 0;
}
static int
parse_coord(char *s, double *lat, double *lon)
{
int withsign = 0;
int saved_errno = errno;
errno = 0;
withsign = (s[0] == '-' || s[0] == '+');
if (s[withsign] != '.' && !isdigit(s[withsign]))
goto bad;
*lat = strtod(s, &s);
if (errno)
return -1;
if (!withsign && (s[0] == 'N' || s[0] == 'S')) {
if (s[0] == 'S')
*lat = -*lat;
s = &s[1];
}
if (s[0] != ' ')
goto bad;
s = &s[1];
withsign = (s[0] == '-' || s[0] == '+');
if (s[withsign] != '.' && !isdigit(s[withsign]))
goto bad;
*lon = strtod(s, &s);
if (errno)
return -1;
if (!withsign && (s[0] == 'E' || s[0] == 'W')) {
if (s[0] == 'W')
*lon = -*lon;
s = &s[1];
}
if (s[0])
goto bad;
errno = saved_errno;
return 0;
bad:
errno = EINVAL;
return -1;
}
int
libcontacts_parse_contact(char *data, struct libcontacts_contact *contact)
{
#define TEST(S, L)\
(!strncmp((test_tmp = (S)), L, sizeof(L) - 1) &&\
(test_tmp[sizeof(L) - 1] == ' ' || test_tmp[sizeof(L) - 1] == '\t'))
#define ADD(LIST)\
do {\
i = 0;\
if (LIST)\
for (; (LIST)[i]; i++);\
if (i > SIZE_MAX / sizeof(*(LIST)) - 2) {\
errno = ENOMEM;\
goto fail;\
}\
temp = realloc((LIST), (i + 2) * sizeof(*(LIST)));\
if (!temp)\
goto fail;\
(LIST) = temp;\
(LIST)[i + 1] = NULL;\
if (!((LIST)[i] = calloc(1, sizeof(**(LIST)))))\
goto fail;\
} while (0)
char *p, *q, *test_tmp;
size_t i = 0; /* initialised to make compiler happy */
time_t t;
void *temp;
int state = 0;
unsigned int u;
unsigned char uc;
memset(contact, 0, sizeof(*contact));
for (p = data; p; p = q) {
q = strpbrk(p, "\n\r\f");
if (q)
*q++ = '\0';
switch ((*p == ' ' || *p == '\t') ? state : 0) {
default:
state = 0;
if (!*p);
else if (TEST(p, "NAME") && !contact->name) {
if (!(contact->name = strdup(getstr(p))))
goto fail;
} else if (TEST(p, "FNAME") && !contact->first_name) {
if (!(contact->first_name = strdup(getstr(p))))
goto fail;
} else if (TEST(p, "LNAME") && !contact->last_name) {
if (!(contact->last_name = strdup(getstr(p))))
goto fail;
} else if (TEST(p, "FLNAME") && !contact->full_name) {
if (!(contact->full_name = strdup(getstr(p))))
goto fail;
} else if (TEST(p, "NICK") && !contact->nickname) {
if (!(contact->nickname = strdup(getstr(p))))
goto fail;
} else if (TEST(p, "PHOTO")) {
if (addstr(&contact->photos, getstr(p)))
goto fail;
} else if (TEST(p, "GROUP")) {
if (addstr(&contact->groups, getstr(p)))
goto fail;
} else if (TEST(p, "NOTES")) {
if (appendstr(&contact->notes, getstr(p)))
goto fail;
} else if (!strcmp(p, "ORG:")) {
ADD(contact->organisations);
state = 1;
break;
case 1:
if (TEST(unindent(p), "ORG") && !contact->organisations[i]->organisation) {
if (!(contact->organisations[i]->organisation = strdup(getstr(p))))
goto fail;
} else if (TEST(unindent(p), "TITLE") && !contact->organisations[i]->title) {
if (!(contact->organisations[i]->title = strdup(getstr(p))))
goto fail;
} else {
if (addstr(&contact->organisations[i]->unrecognised_data, unindent(p)))
goto fail;
}
break;
} else if (!strcmp(p, "EMAIL:")) {
ADD(contact->emails);
state = 2;
break;
case 2:
if (TEST(unindent(p), "CTX") && !contact->emails[i]->context) {
if (!(contact->emails[i]->context = strdup(getstr(p))))
goto fail;
} else if (TEST(unindent(p), "ADDR") && !contact->emails[i]->address) {
if (!(contact->emails[i]->address = strdup(getstr(p))))
goto fail;
} else {
if (addstr(&contact->emails[i]->unrecognised_data, unindent(p)))
goto fail;
}
break;
} else if (!strcmp(p, "KEY:")) {
ADD(contact->pgpkeys);
state = 3;
break;
case 3:
if (TEST(unindent(p), "CTX") && !contact->pgpkeys[i]->context) {
if (!(contact->pgpkeys[i]->context = strdup(getstr(p))))
goto fail;
} else if (TEST(unindent(p), "ID") && !contact->pgpkeys[i]->id) {
if (!(contact->pgpkeys[i]->id = strdup(getstr(p))))
goto fail;
} else {
if (addstr(&contact->pgpkeys[i]->unrecognised_data, unindent(p)))
goto fail;
}
break;
} else if (!strcmp(p, "PHONE:")) {
ADD(contact->numbers);
state = 4;
break;
case 4:
if (TEST(unindent(p), "CTX") && !contact->numbers[i]->context) {
if (!(contact->numbers[i]->context = strdup(getstr(p))))
goto fail;
} else if (TEST(unindent(p), "NUMBER") && !contact->numbers[i]->number) {
if (!(contact->numbers[i]->number = strdup(getstr(p))))
goto fail;
} else if (!strcmp(unindent(p), "MOBILE")) {
contact->numbers[i]->is_mobile = 1;
} else if (!strcmp(unindent(p), "FAX")) {
contact->numbers[i]->is_facsimile = 1;
} else {
if (addstr(&contact->numbers[i]->unrecognised_data, unindent(p)))
goto fail;
}
break;
} else if (!strcmp(p, "ADDR:")) {
ADD(contact->addresses);
state = 5;
break;
case 5:
if (TEST(unindent(p), "CTX") && !contact->addresses[i]->context) {
if (!(contact->addresses[i]->context = strdup(getstr(p))))
goto fail;
} else if (TEST(unindent(p), "COUNTRY") && !contact->addresses[i]->country) {
if (!(contact->addresses[i]->country = strdup(getstr(p))))
goto fail;
} else if (TEST(unindent(p), "C/O") && !contact->addresses[i]->care_of) {
if (!(contact->addresses[i]->care_of = strdup(getstr(p))))
goto fail;
} else if (TEST(unindent(p), "ADDR") && !contact->addresses[i]->address) {
if (!(contact->addresses[i]->address = strdup(getstr(p))))
goto fail;
} else if (TEST(unindent(p), "CODE") && !contact->addresses[i]->postcode) {
if (!(contact->addresses[i]->postcode = strdup(getstr(p))))
goto fail;
} else if (TEST(unindent(p), "CITY") && !contact->addresses[i]->city) {
if (!(contact->addresses[i]->city = strdup(getstr(p))))
goto fail;
} else if (TEST(unindent(p), "COORD") && !contact->addresses[i]->have_coordinates) {
if (!parse_coord(getstr(p),
&contact->addresses[i]->latitude,
&contact->addresses[i]->longitude)) {
contact->addresses[i]->have_coordinates = 1;
} else {
if (addstr(&contact->addresses[i]->unrecognised_data, unindent(p)))
goto fail;
}
} else {
if (addstr(&contact->addresses[i]->unrecognised_data, unindent(p)))
goto fail;
}
break;
} else if (!strcmp(p, "SITE:")) {
ADD(contact->sites);
state = 6;
break;
case 6:
if (TEST(unindent(p), "CTX") && !contact->sites[i]->context) {
if (!(contact->sites[i]->context = strdup(getstr(p))))
goto fail;
} else if (TEST(unindent(p), "ADDR") && !contact->sites[i]->address) {
if (!(contact->sites[i]->address = strdup(getstr(p))))
goto fail;
} else {
if (addstr(&contact->sites[i]->unrecognised_data, unindent(p)))
goto fail;
}
break;
} else if (!strcmp(p, "CHAT:")) {
ADD(contact->chats);
state = 7;
break;
case 7:
if (TEST(unindent(p), "CTX") && !contact->chats[i]->context) {
if (!(contact->chats[i]->context = strdup(getstr(p))))
goto fail;
} else if (TEST(unindent(p), "SRV") && !contact->chats[i]->service) {
if (!(contact->chats[i]->service = strdup(getstr(p))))
goto fail;
} else if (TEST(unindent(p), "ADDR") && !contact->chats[i]->address) {
if (!(contact->chats[i]->address = strdup(getstr(p))))
goto fail;
} else {
if (addstr(&contact->chats[i]->unrecognised_data, unindent(p)))
goto fail;
}
break;
} else if (!strcmp(p, "BLOCK:")) {
ADD(contact->blocks);
state = 8;
break;
case 8:
if (TEST(unindent(p), "SRV") && !contact->blocks[i]->service) {
if (!(contact->blocks[i]->service = strdup(getstr(p))))
goto fail;
} else if (!strcmp(unindent(p), "OFF") && !contact->blocks[i]->shadow_block) {
contact->blocks[i]->shadow_block = LIBCONTACTS_BLOCK_OFF;
} else if (!strcmp(unindent(p), "BUSY") && !contact->blocks[i]->shadow_block) {
contact->blocks[i]->shadow_block = LIBCONTACTS_BLOCK_BUSY;
} else if (!strcmp(unindent(p), "IGNORE") && !contact->blocks[i]->shadow_block) {
contact->blocks[i]->shadow_block = LIBCONTACTS_BLOCK_IGNORE;
} else if (!strcmp(unindent(p), "EXPLICIT")) {
contact->blocks[i]->explicit = 1;
} else if (TEST(unindent(p), "ASK") && !contact->blocks[i]->soft_unblock && (t = gettime(p))) {
contact->blocks[i]->soft_unblock = t;
} else if (TEST(unindent(p), "REMOVE") && !contact->blocks[i]->hard_unblock && (t = gettime(p))) {
contact->blocks[i]->hard_unblock = t;
} else {
if (addstr(&contact->blocks[i]->unrecognised_data, unindent(p)))
goto fail;
}
break;
} else if (!strcmp(p, "BIRTH:") && !contact->birthday) {
contact->birthday = calloc(1, sizeof(*contact->birthday));
if (!contact->birthday)
goto fail;
state = 9;
break;
case 9:
if (TEST(unindent(p), "YEAR") && !contact->birthday->year && (u = getposuint(p))) {
contact->birthday->year = u;
} else if (TEST(unindent(p), "MONTH") && !contact->birthday->month && (uc = getposuchar(p))) {
contact->birthday->month = uc;
} else if (TEST(unindent(p), "DAY") && !contact->birthday->day && (uc = getposuchar(p))) {
contact->birthday->day = uc;
} else if (!strcmp(unindent(p), "EARLY")) {
contact->birthday->before_on_common = 1;
} else {
if (addstr(&contact->birthday->unrecognised_data, unindent(p)))
goto fail;
}
break;
} else if (!strcmp(p, "ICE")) {
contact->in_case_of_emergency = 1;
} else if (!strcmp(p, "NPERSON") && !contact->gender) {
contact->gender = LIBCONTACTS_NOT_A_PERSON;
} else if (!strcmp(p, "MALE") && !contact->gender) {
contact->gender = LIBCONTACTS_MALE;
} else if (!strcmp(p, "FEMALE") && !contact->gender) {
contact->gender = LIBCONTACTS_FEMALE;
} else {
if (addstr(&contact->unrecognised_data, p))
goto fail;
}
}
}
return 0;
fail:
libcontacts_contact_destroy(contact);
memset(contact, 0, sizeof(*contact));
return -1;
#undef TEST
#undef ADD
}