diff options
-rw-r--r-- | .gitignore | 7 | ||||
-rw-r--r-- | LICENSE | 15 | ||||
-rw-r--r-- | Makefile | 56 | ||||
-rw-r--r-- | TODO | 5 | ||||
-rw-r--r-- | common.h | 23 | ||||
-rw-r--r-- | config.mk | 8 | ||||
-rw-r--r-- | libcontacts.h | 247 | ||||
-rw-r--r-- | libcontacts_address_destroy.c | 15 | ||||
-rw-r--r-- | libcontacts_birthday_destroy.c | 9 | ||||
-rw-r--r-- | libcontacts_chat_destroy.c | 12 | ||||
-rw-r--r-- | libcontacts_contact_destroy.c | 25 | ||||
-rw-r--r-- | libcontacts_email_destroy.c | 11 | ||||
-rw-r--r-- | libcontacts_format_contact.c | 197 | ||||
-rw-r--r-- | libcontacts_get_path.c | 23 | ||||
-rw-r--r-- | libcontacts_list_contacts.c | 61 | ||||
-rw-r--r-- | libcontacts_load_contact.c | 65 | ||||
-rw-r--r-- | libcontacts_load_contacts.c | 60 | ||||
-rw-r--r-- | libcontacts_number_destroy.c | 11 | ||||
-rw-r--r-- | libcontacts_organisation_destroy.c | 11 | ||||
-rw-r--r-- | libcontacts_parse_contact.c | 400 | ||||
-rw-r--r-- | libcontacts_pgpkey_destroy.c | 11 | ||||
-rw-r--r-- | libcontacts_save_contact.c | 93 | ||||
-rw-r--r-- | libcontacts_site_destroy.c | 11 |
23 files changed, 1376 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..baa5218 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +*\#* +*~ +*.o +*.a +*.so +*.su +*.lo @@ -0,0 +1,15 @@ +ISC License + +© 2021 Mattias Andrée <maandree@kth.se> + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..33521e9 --- /dev/null +++ b/Makefile @@ -0,0 +1,56 @@ +.POSIX: + +CONFIGFILE = config.mk +include $(CONFIGFILE) + + +OBJ =\ + libcontacts_address_destroy.o\ + libcontacts_birthday_destroy.o\ + libcontacts_chat_destroy.o\ + libcontacts_contact_destroy.o\ + libcontacts_email_destroy.o\ + libcontacts_format_contact.o\ + libcontacts_get_path.o\ + libcontacts_list_contacts.o\ + libcontacts_load_contact.o\ + libcontacts_load_contacts.o\ + libcontacts_number_destroy.o\ + libcontacts_organisation_destroy.o\ + libcontacts_parse_contact.o\ + libcontacts_pgpkey_destroy.o\ + libcontacts_save_contact.o\ + libcontacts_site_destroy.o + +HDR =\ + common.h\ + libcontacts.h + + +all: libcontacts.a +$(OBJ): $($@:.o=.c) $(HDR) + +libcontacts.a: $(OBJ) + $(AR) rc $@ $(OBJ) + $(AR) -s $@ + +.c.o: + $(CC) -c -o $@ $< $(CFLAGS) $(CPPFLAGS) + +install: libcontacts.a + mkdir -p -- "$(DESTDIR)$(PREFIX)/lib" + mkdir -p -- "$(DESTDIR)$(PREFIX)/include" + cp -- libcontacts.a "$(DESTDIR)$(PREFIX)/lib/" + cp -- libcontacts.h "$(DESTDIR)$(PREFIX)/include/" + +uninstall: + -rm -f -- "$(DESTDIR)$(PREFIX)/lib/libcontacts.a" + -rm -f -- "$(DESTDIR)$(PREFIX)/include/libcontacts.h" + +clean: + -rm -f -- *.o *.a *.lo *.so *.su + +.SUFFIXES: +.SUFFIXES: .c .o + +.PHONY: all install uninstall clean @@ -0,0 +1,5 @@ +Add dynamically linked library +Add man pages +Add readme +Add tests +Add cache (only number->ids, id->photos, number->photos) diff --git a/common.h b/common.h new file mode 100644 index 0000000..bf886cd --- /dev/null +++ b/common.h @@ -0,0 +1,23 @@ +/* See LICENSE file for copyright and license details. */ +#include "libcontacts.h" + +#include <alloca.h> +#include <ctype.h> +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + + +#define DESTROY_ALL(LIST, FUNC)\ + do {\ + void *destroy_all_temp__;\ + if ((destroy_all_temp__ = (LIST))) {\ + for (; *(LIST); (LIST)++)\ + FUNC(*(LIST));\ + free(destroy_all_temp__);\ + }\ + } while (0) diff --git a/config.mk b/config.mk new file mode 100644 index 0000000..9404b9a --- /dev/null +++ b/config.mk @@ -0,0 +1,8 @@ +PREFIX = /usr +MANPREFIX = $(PREFIX)/share/man + +CC = cc + +CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700 +CFLAGS = -std=c99 -O2 +LDFLAGS = -s diff --git a/libcontacts.h b/libcontacts.h new file mode 100644 index 0000000..cd65bf4 --- /dev/null +++ b/libcontacts.h @@ -0,0 +1,247 @@ +/* See LICENSE file for copyright and license details. */ +#ifndef LIBCONTACTS_H +#define LIBCONTACTS_H + +#include <pwd.h> +#include <time.h> + + +/** + * Gender of contact + */ +enum libcontacts_gender { + LIBCONTACTS_UNSPECIFIED_GENDER, + LIBCONTACTS_NOT_A_PERSON, + LIBCONTACTS_MALE, + LIBCONTACTS_FEMALE +}; + +/** + * Organisation information for contact + */ +struct libcontacts_organisation { + char *organisation; /* Orginisation the contact belongs to */ + char *title; /* The contact's title/role in the orginisation */ + char **unrecognised_data; /* Data not recognised by the library */ +}; + +/** + * E-mail information for contact + */ +struct libcontacts_email { + char *context; /* Work e-mail (which job?)? Personal e-mail? … */ + char *address; /* E-mail address */ + char **unrecognised_data; /* Data not recognised by the library */ +}; + +/** + * PGP-keys for contact + */ +struct libcontacts_pgpkey { + char *context; /* Work key (which job?)? Personal key? … */ + char *id; /* The key's fingerprint */ + char **unrecognised_data; /* Data not recognised by the library */ +}; + +/** + * Telephone number for contact + */ +struct libcontacts_number { + char *context; /* Work phone (which job?)? Personal phone (mobile?, which home?)? … */ + char *number; /* The telephone number */ + int is_mobile; /* Is this a mobile phone (does it receive SMS)? */ + int is_facsimile; /* Is this facsimile (fax) machine? */ + char **unrecognised_data; /* Data not recognised by the library */ +}; + +/** + * Address for contact + */ +struct libcontacts_address { + char *context; /* Work address (which job)? Home? Summer cabin? … */ + char *country; /* Which country? */ + char *care_of; /* Care of address, if any */ + char *address; /* Address, all lines in one */ + char *postcode; /* Post code */ + char *city; /* Which city is the post code tied to? */ + int have_coordinates; /* Are `.latitude` and `.longitude` defined? */ + double latitude; /* Latitudal GPS coordinate */ + double longitude; /* Longitudal GPS coordinate */ + char **unrecognised_data; /* Data not recognised by the library */ +}; + +/** + * Site (e.g. web and gopher) for contact + */ +struct libcontacts_site { + char *context; /* Work site (which job?)? Personal site (what is it used for?)? … */ + char *address; /* Address to the site, including protocol */ + char **unrecognised_data; /* Data not recognised by the library */ +}; + +/** + * Chat address for contact + */ +struct libcontacts_chat { + char *context; /* Work account (which job?)? Personal account? … */ + char *service; /* What service is the account */ + char *address; /* What is the name/address/number of the account */ + char **unrecognised_data; /* Data not recognised by the library */ +}; + +/** + * Birthday of contact + */ +struct libcontacts_birthday { + unsigned int year; /* asis, 0 for unknown */ + unsigned char month; /* january = 1, 0 for unknown */ + unsigned char day; /* asis, 0 for unknown */ +}; + + +/** + * Contact information + */ +struct libcontacts_contact { + /** + * The ID of the contact, used to select filename + * + * Must not begin with a dot, except if it is: + * ".me" - The user himself. + * ".nobody" - Unused data, such as created groups without any members. + * Additionally, it must not end with ~ or contain an /, + * and it should be short enough for a filename + */ + char *id; + + /** + * The name of the contact as it should be displayed + */ + char *name; + + /** + * The first name of the contact + */ + char *first_name; + + /** + * The last name(s) of the contact, if any + */ + char *last_name; + + /** + * Nick name of the contact, if any + */ + char *nickname; + + /** + * Pathname to photoes of the contact, use + * absolute paths or paths relative to the + * user's home directory + * + * Applications may desired which photo to + * use based on their size, but put the in + * order of preference + * + * For each telephone number, applications + * should only use photos that are shared + * exactly between the contacts that share + * that telephone number + */ + char **photos; + + /** + * Groups the contact is a member of + */ + char **groups; + + /** + * Personal notes about the contact + */ + char *notes; + + /** + * Organisation information for contact + */ + struct libcontacts_organisation **organisations; + + /** + * E-mail information for contact + */ + struct libcontacts_email **emails; + + /** + * PGP-keys for the contact + */ + struct libcontacts_pgpkey **pgpkeys; + + /** + * Telephone number for the contact + * + * Phone number can be shared, in case of an + * incoming call where the phone number is + * shared, the application shall list contacts + * that phone number + */ + struct libcontacts_number **numbers; + + /** + * Address for the contact + */ + struct libcontacts_address **addresses; + + /** + * Site (e.g. web and gopher) for the contact + */ + struct libcontacts_site **sites; + + /** + * Chat address for the contact + */ + struct libcontacts_chat **chats; + + /** + * Birthday of the contact + */ + struct libcontacts_birthday *birthday; + + /** + * Whether the contact shall be listed as an ICE + * (In Case of Emergency) contact that can be + * view without unlocking the phone + */ + int in_case_of_emergency; + + /** + * The gender of the contact + */ + enum libcontacts_gender gender; + + /** + * Data not recognised by the library + */ + char **unrecognised_data; +}; + + +void libcontacts_contact_destroy(struct libcontacts_contact *); +int libcontacts_list_contacts(char ***, const struct passwd *); +int libcontacts_load_contact(const char *, struct libcontacts_contact *, const struct passwd *); /* errno = 0 if malformatted */ +int libcontacts_load_contacts(struct libcontacts_contact ***, const struct passwd *); +int libcontacts_save_contact(struct libcontacts_contact *, const struct passwd *); + +char *libcontacts_get_path(const char *, const struct passwd *); +int libcontacts_parse_contact(char *, struct libcontacts_contact *); /* does not load .id, not stored in file, but is the filename */ +int libcontacts_format_contact(const struct libcontacts_contact *, char **); + +void libcontacts_organisation_destroy(struct libcontacts_organisation *); +void libcontacts_email_destroy(struct libcontacts_email *); +void libcontacts_pgpkey_destroy(struct libcontacts_pgpkey *); +void libcontacts_number_destroy(struct libcontacts_number *); +void libcontacts_address_destroy(struct libcontacts_address *); +void libcontacts_site_destroy(struct libcontacts_site *); +void libcontacts_chat_destroy(struct libcontacts_chat *); +void libcontacts_birthday_destroy(struct libcontacts_birthday *); + + +#endif diff --git a/libcontacts_address_destroy.c b/libcontacts_address_destroy.c new file mode 100644 index 0000000..1a79f21 --- /dev/null +++ b/libcontacts_address_destroy.c @@ -0,0 +1,15 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +void +libcontacts_address_destroy(struct libcontacts_address *this) +{ + free(this->context); + free(this->country); + free(this->care_of); + free(this->address); + free(this->postcode); + free(this->city); + DESTROY_ALL(this->unrecognised_data, free); +} diff --git a/libcontacts_birthday_destroy.c b/libcontacts_birthday_destroy.c new file mode 100644 index 0000000..c2c1f95 --- /dev/null +++ b/libcontacts_birthday_destroy.c @@ -0,0 +1,9 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +void +libcontacts_birthday_destroy(struct libcontacts_birthday *this) +{ + (void) this; +} diff --git a/libcontacts_chat_destroy.c b/libcontacts_chat_destroy.c new file mode 100644 index 0000000..7e5498a --- /dev/null +++ b/libcontacts_chat_destroy.c @@ -0,0 +1,12 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +void +libcontacts_chat_destroy(struct libcontacts_chat *this) +{ + free(this->context); + free(this->service); + free(this->address); + DESTROY_ALL(this->unrecognised_data, free); +} diff --git a/libcontacts_contact_destroy.c b/libcontacts_contact_destroy.c new file mode 100644 index 0000000..ef208d5 --- /dev/null +++ b/libcontacts_contact_destroy.c @@ -0,0 +1,25 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +void +libcontacts_contact_destroy(struct libcontacts_contact *this) +{ + free(this->id); + free(this->name); + free(this->first_name); + free(this->last_name); + free(this->nickname); + DESTROY_ALL(this->photos, free); + DESTROY_ALL(this->groups, free); + free(this->notes); + DESTROY_ALL(this->organisations, libcontacts_organisation_destroy); + DESTROY_ALL(this->emails, libcontacts_email_destroy); + DESTROY_ALL(this->pgpkeys, libcontacts_pgpkey_destroy); + DESTROY_ALL(this->numbers, libcontacts_number_destroy); + DESTROY_ALL(this->addresses, libcontacts_address_destroy); + DESTROY_ALL(this->sites, libcontacts_site_destroy); + DESTROY_ALL(this->chats, libcontacts_chat_destroy); + libcontacts_birthday_destroy(this->birthday); + DESTROY_ALL(this->unrecognised_data, free); +} diff --git a/libcontacts_email_destroy.c b/libcontacts_email_destroy.c new file mode 100644 index 0000000..329cd62 --- /dev/null +++ b/libcontacts_email_destroy.c @@ -0,0 +1,11 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +void +libcontacts_email_destroy(struct libcontacts_email *this) +{ + free(this->context); + free(this->address); + DESTROY_ALL(this->unrecognised_data, free); +} diff --git a/libcontacts_format_contact.c b/libcontacts_format_contact.c new file mode 100644 index 0000000..1752026 --- /dev/null +++ b/libcontacts_format_contact.c @@ -0,0 +1,197 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +int +libcontacts_format_contact(const struct libcontacts_contact *contact, char **datap) +{ + FILE *fp; + size_t siz = 0; + char **list, *p, *q; + const char *suffix; + struct libcontacts_organisation **organisations, *organisation; + struct libcontacts_email **emails, *email; + struct libcontacts_pgpkey **pgpkeys, *pgpkey; + struct libcontacts_number **numbers, *number; + struct libcontacts_address **addresses, *address; + struct libcontacts_site **sites, *site; + struct libcontacts_chat **chats, *chat; + + *datap = NULL; + fp = open_memstream(datap, &siz); + if (!fp) + return -1; + + if (contact->name) + fprintf(fp, "NAME %s\n", contact->name); + + if (contact->first_name) + fprintf(fp, "FNAME %s\n", contact->first_name); + + if (contact->last_name) + fprintf(fp, "LNAME %s\n", contact->last_name); + + if (contact->nickname) + fprintf(fp, "NICK %s\n", contact->nickname); + + if ((list = contact->photos)) + for (; *list; list++) + fprintf(fp, "PHOTO %s\n", *list); + + if ((list = contact->groups)) + for (; *list; list++) + fprintf(fp, "GROUP %s\n", *list); + + if ((p = contact->notes)) { + for (; *p; p = q) { + q = strchr(p, '\n'); + suffix = q ? "" : "\n"; + q = q ? &q[1] : strchr(p, '\0'); + fprintf(fp, "NOTES %.*s%s", (int)(q - p), p, suffix); + } + } + + if ((organisations = contact->organisations)) { + for (; (organisation = *organisations); organisations++) { + fprintf(fp, "ORG:\n"); + if (organisation->organisation) + fprintf(fp, "\tORG %s\n", *organisation->organisation); + if (organisation->title) + fprintf(fp, "\tTITLE %s\n", organisation->title); + if ((list = organisation->unrecognised_data)) + for (; *list; list++) + fprintf(fp, "\t%s\n", *list); + } + } + + if ((emails = contact->emails)) { + for (; (email = *emails); emails++) { + fprintf(fp, "EMAIL:\n"); + if (email->context) + fprintf(fp, "\tCTX %s\n", email->context); + if (email->address) + fprintf(fp, "\tADDR %s\n", email->address); + if ((list = email->unrecognised_data)) + for (; *list; list++) + fprintf(fp, "\t%s\n", *list); + } + } + + if ((pgpkeys = contact->pgpkeys)) { + for (; (pgpkey = *pgpkeys); pgpkeys++) { + fprintf(fp, "KEY:\n"); + if (pgpkey->context) + fprintf(fp, "\tCTX %s\n", pgpkey->context); + if (pgpkey->id) + fprintf(fp, "\tID %s\n", pgpkey->id); + if ((list = pgpkey->unrecognised_data)) + for (; *list; list++) + fprintf(fp, "\t%s\n", *list); + } + } + + if ((numbers = contact->numbers)) { + for (; (number = *numbers); numbers++) { + fprintf(fp, "PHONE:\n"); + if (number->context) + fprintf(fp, "\tCTX %s\n", number->context); + if (number->number) + fprintf(fp, "\tNUMBER %s\n", number->number); + if (number->is_mobile) + fprintf(fp, "\tMOBILE\n"); + if (number->is_facsimile) + fprintf(fp, "\tFAX\n"); + if ((list = number->unrecognised_data)) + for (; *list; list++) + fprintf(fp, "\t%s\n", *list); + } + } + + if ((addresses = contact->addresses)) { + for (; (address = *addresses); addresses++) { + fprintf(fp, "ADDR:\n"); + if (address->context) + fprintf(fp, "\tCTX %s\n", address->context); + if (address->country) + fprintf(fp, "\tCOUNTRY %s\n", address->country); + if (address->care_of) + fprintf(fp, "\tC/O %s\n", address->care_of); + if (address->address) + fprintf(fp, "\tADDR %s\n", address->address); + if (address->postcode) + fprintf(fp, "\tCODE %s\n", address->postcode); + if (address->city) + fprintf(fp, "\tCITY %s\n", address->city); + if (address->have_coordinates) + fprintf(fp, "\tCOORD %.20lg %.20lg\n", address->latitude, address->longitude); + if ((list = address->unrecognised_data)) + for (; *list; list++) + fprintf(fp, "\t%s\n", *list); + } + } + + if ((sites = contact->sites)) { + for (; (site = *sites); sites++) { + fprintf(fp, "SITE:\n"); + if (site->context) + fprintf(fp, "\tCTX %s\n", site->context); + if (site->address) + fprintf(fp, "\tADDR %s\n", site->address); + if ((list = site->unrecognised_data)) + for (; *list; list++) + fprintf(fp, "\t%s\n", *list); + } + } + + if ((chats = contact->chats)) { + for (; (chat = *chats); chats++) { + fprintf(fp, "CHAT:\n"); + if (chat->context) + fprintf(fp, "\tCTX %s\n", chat->context); + if (chat->service) + fprintf(fp, "\tSRV %s\n", chat->service); + if (chat->address) + fprintf(fp, "\tADDR %s\n", chat->address); + if ((list = chat->unrecognised_data)) + for (; *list; list++) + fprintf(fp, "\t%s\n", *list); + } + } + + if (contact->birthday) { + if (contact->birthday->year && contact->birthday->day) { + fprintf(fp, "BIRTH %04i-%02i-%02i\n", + contact->birthday->year, contact->birthday->month, contact->birthday->day); + } else if (contact->birthday->year && contact->birthday->month) { + fprintf(fp, "BIRTH %04i-%02i\n", contact->birthday->year, contact->birthday->month); + } else if (contact->birthday->year) { + fprintf(fp, "BIRTH %04i\n", contact->birthday->year); + } else if (contact->birthday->day) { + fprintf(fp, "BIRTH %02i-%02\n", contact->birthday->month, contact->birthday->day); + } else if (contact->birthday->month) { + fprintf(fp, "BIRTH %02i\n", contact->birthday->month); + } + } + + if (contact->in_case_of_emergency) + fprintf(fp, "ICE\n"); + + if (contact->gender == LIBCONTACTS_NOT_A_PERSON) + fprintf(fp, "NPERSON\n"); + else if (contact->gender == LIBCONTACTS_MALE) + fprintf(fp, "MALE\n"); + else if (contact->gender == LIBCONTACTS_FEMALE) + fprintf(fp, "FEMALE\n"); + + if ((list = contact->unrecognised_data)) + for (; *list; list++) + fprintf(fp, "%s\n", *list); + + if (fclose(fp)) { + free(*datap); + *datap = NULL; + return -1; + } + + return 0; +} diff --git a/libcontacts_get_path.c b/libcontacts_get_path.c new file mode 100644 index 0000000..cc20b7d --- /dev/null +++ b/libcontacts_get_path.c @@ -0,0 +1,23 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +char * +libcontacts_get_path(const char *id, const struct passwd *user) +{ + size_t len; + char *buf; + + if (!id || !user || !user->pw_dir || !*user->pw_dir) { + errno = EINVAL; + return NULL; + } + + len = strlen(user->pw_dir) + sizeof("/.config/contacts/") + strlen(id); + buf = malloc(len); + if (!buf) + return NULL; + + stpcpy(stpcpy(stpcpy(buf, user->pw_dir), "/.config/contacts/"), id); + return buf; +} diff --git a/libcontacts_list_contacts.c b/libcontacts_list_contacts.c new file mode 100644 index 0000000..e4794f0 --- /dev/null +++ b/libcontacts_list_contacts.c @@ -0,0 +1,61 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +int +libcontacts_list_contacts(char ***idsp, const struct passwd *user) +{ + char *dirnam; + DIR *dir; + struct dirent *f; + size_t i = 0; + void *new; + int saved_errno = errno; + + *idsp = NULL; + + dirnam = libcontacts_get_path("", user); + if (!dirnam) + return -1; + + dir = opendir(dirnam); + if (!dir) { + free(dirnam); + return -1; + } + + goto start; + while ((f = readdir(dir))) { + if (strchr(f->d_name, '.')) + continue; + if (!f->d_name[0] || strchr(f->d_name, '\0')[-1] == '~') + continue; + if (!((*idsp)[i++] = strdup(f->d_name))) + goto fail; + start: + new = realloc(*idsp, (i + 1) * sizeof(**idsp)); + if (!new) + goto fail; + *idsp = new; + } + (*idsp)[i] = NULL; + + if (errno) + goto fail; + closedir(dir); + free(dirnam); + errno = saved_errno; + return 0; + +fail: + saved_errno = errno; + closedir(dir); + free(dirnam); + if (*idsp) { + for (i = 0; (*idsp)[i]; i++) + free((*idsp)[i]); + free(*idsp); + } + errno = saved_errno; + return -1; +} diff --git a/libcontacts_load_contact.c b/libcontacts_load_contact.c new file mode 100644 index 0000000..2d6f933 --- /dev/null +++ b/libcontacts_load_contact.c @@ -0,0 +1,65 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +int +libcontacts_load_contact(const char *id, struct libcontacts_contact *contact, const struct passwd *user) +{ + int ret, fd, saved_errno; + char *data = NULL, *path; + size_t p = 0, n = 0; + ssize_t r; + void *new; + + if (!contact) { + errno = EINVAL; + return -1; + } + + path = libcontacts_get_path(id, user); + if (!path) + return -1; + fd = open(path, O_RDONLY); + free(path); + if (fd < 0) + return -1; + + for (;;) { + if (p == n) { + new = realloc(data, n + (8 << 10) + 1); + if (!new) + goto fail; + n += 8 << 10; + } + r = read(fd, &data[p], n - p); + if (r <= 0) { + if (r) + goto fail; + break; + } + p += (size_t)r; + } + data[p] = '\0'; + + if (memchr(data, 0, p)) { + errno = 0; + goto fail; + } + + close(fd); + ret = libcontacts_parse_contact(data, contact); + free(data); + if (!ret && !(contact->id = strdup(id))) { + libcontacts_contact_destroy(contact); + memset(contact, 0, sizeof(*contact)); + return -1; + } + return ret; + +fail: + saved_errno = errno; + free(data); + close(fd); + errno = saved_errno; + return -1; +} diff --git a/libcontacts_load_contacts.c b/libcontacts_load_contacts.c new file mode 100644 index 0000000..91f8c56 --- /dev/null +++ b/libcontacts_load_contacts.c @@ -0,0 +1,60 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +int +libcontacts_load_contacts(struct libcontacts_contact ***contactsp, const struct passwd *user) +{ + int saved_errno = errno; + char **ids; + void *temp; + size_t i, j, n; + struct libcontacts_contact contact; + + *contactsp = NULL; + + if (libcontacts_list_contacts(&ids, user)) + return -1; + + for (n = 0; ids[n]; n++); + *contactsp = calloc(n, sizeof(**contactsp)); + if (!*contactsp) + goto fail; + + for (i = j = 0; i < n; i++) { + memset(&contact, 0, sizeof(contact)); + if (libcontacts_load_contact(ids[i], &contact, user)) { + switch (errno) { + case ENOENT: + case EACCES: + continue; + default: + goto fail; + } + } + (*contactsp)[j] = calloc(1, sizeof(***contactsp)); + if (!(*contactsp)[j]) + goto fail; + *(*contactsp)[j++] = contact; + } + + for (i = 0; i < n; i++) + free(ids[i]); + free(ids); + errno = saved_errno; + return 0; + +fail: + for (i = 0; i < n; i++) + free(ids[i]); + free(ids); + if ((temp = *contactsp)) { + for (; **contactsp; ++*contactsp) { + libcontacts_contact_destroy(**contactsp); + free(**contactsp); + } + } + free(temp); + *contactsp = NULL; + return -1; +} diff --git a/libcontacts_number_destroy.c b/libcontacts_number_destroy.c new file mode 100644 index 0000000..64b84cd --- /dev/null +++ b/libcontacts_number_destroy.c @@ -0,0 +1,11 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +void +libcontacts_number_destroy(struct libcontacts_number *this) +{ + free(this->context); + free(this->number); + DESTROY_ALL(this->unrecognised_data, free); +} diff --git a/libcontacts_organisation_destroy.c b/libcontacts_organisation_destroy.c new file mode 100644 index 0000000..94d4a3a --- /dev/null +++ b/libcontacts_organisation_destroy.c @@ -0,0 +1,11 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +void +libcontacts_organisation_destroy(struct libcontacts_organisation *this) +{ + free(this->organisation); + free(this->title); + DESTROY_ALL(this->unrecognised_data, free); +} diff --git a/libcontacts_parse_contact.c b/libcontacts_parse_contact.c new file mode 100644 index 0000000..da39432 --- /dev/null +++ b/libcontacts_parse_contact.c @@ -0,0 +1,400 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +static char * +getstr(char *data) +{ + while (*data == ' ' || *data == '\t') data++; + return &strchr(data, ' ')[1]; +} + +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; + add = strdup(new); + if (!add) + return -1; + if (!*listp) { + *listp = calloc(2, sizeof(char *)); + **listp = add; + } else { + for (i = 0; (*listp)[i]; i++); + temp = realloc(*listp, (i + 2) * sizeof(char *)); + if (!temp) { + free(add); + return -1; + } + *listp = temp; + (*listp)[i + 0] = add; + (*listp)[i + 1] = NULL; + } + return 0; +} + +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; + + 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; +} + +static int +parse_birth(char *s, struct libcontacts_birthday *birth) +{ + birth->year = 0; + birth->month = 0; + birth->day = 0; + + if (isdigit(s[0]) && isdigit(s[1]) && isdigit(s[2]) && isdigit(s[3])) { + birth->year = (s[0] & 15) * 1000; + birth->year += (s[1] & 15) * 100; + birth->year += (s[2] & 15) * 10; + birth->year += (s[3] & 15) * 1; + if (s[4] == '-') + s = &s[5]; + else if (s[4]) + goto bad; + else + return 0; + } + + if (isdigit(s[0]) && isdigit(s[1])) { + birth->month = (s[0] & 15) * 10; + birth->month += (s[1] & 15) * 1; + if (s[2] == '-') + s = &s[3]; + else if (s[2]) + goto bad; + else + return 0; + } + + if (isdigit(s[0]) && isdigit(s[1])) { + birth->day = (s[0] & 15) * 10; + birth->day += (s[1] & 15) * 1; + if (!s[2]) + return 0; + } + +bad: + errno = EINVAL; + return -1; +} + + +int +libcontacts_parse_contact(char *data, struct libcontacts_contact *contact) +{ +#define TEST(S, L)\ + (!strncmp((S), L, sizeof(L) - 1) &&\ + (S[sizeof(L)] == ' ' || S[sizeof(L)] == '\t')) + +#define ADD(LIST)\ + do {\ + for (i = 0; (LIST) && (LIST)[i]; i++);\ + 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; + size_t i; + void *temp; + int state = 0; + + 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, "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, getstr(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, getstr(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, getstr(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, getstr(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, getstr(p))) + goto fail; + } + } else { + if (addstr(&contact->addresses[i]->unrecognised_data, getstr(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, getstr(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, getstr(p))) + goto fail; + } + break; + + } else if (TEST(p, "BIRTH") && !contact->birthday) { + contact->birthday = malloc(sizeof(*contact->birthday)); + if (!contact->birthday) + goto fail; + if (!parse_birth(getstr(p), contact->birthday)) { + free(contact->birthday); + contact->birthday = NULL; + if (addstr(&contact->unrecognised_data, getstr(p))) + goto fail; + } + + } 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, getstr(p))) + goto fail; + } + } + } + + return 0; + +fail: + libcontacts_contact_destroy(contact); + memset(contact, 0, sizeof(*contact)); + return -1; + +#undef TEST +#undef ADD +} diff --git a/libcontacts_pgpkey_destroy.c b/libcontacts_pgpkey_destroy.c new file mode 100644 index 0000000..aa465ab --- /dev/null +++ b/libcontacts_pgpkey_destroy.c @@ -0,0 +1,11 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +void +libcontacts_pgpkey_destroy(struct libcontacts_pgpkey *this) +{ + free(this->context); + free(this->id); + DESTROY_ALL(this->unrecognised_data, free); +} diff --git a/libcontacts_save_contact.c b/libcontacts_save_contact.c new file mode 100644 index 0000000..5d2fcd7 --- /dev/null +++ b/libcontacts_save_contact.c @@ -0,0 +1,93 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +int +libcontacts_save_contact(struct libcontacts_contact *contact, const struct passwd *user) +{ + char *data = NULL, *path = NULL, *tmppath; + int oflags = O_WRONLY | O_CREAT | O_TRUNC; + int fd = -1, saved_errno = errno; + ssize_t r; + size_t p, n, num = 0; + char *basenam = NULL; + + if (libcontacts_format_contact(contact, &data)) + goto fail; + + if (!contact->id) { + oflags = O_WRONLY | O_CREAT | O_EXCL; + generate_id: + if (!num) { + if (!contact->name || !*contact->name) { + contact->id = strdup("unnamed"); + if (!contact->id) + goto fail; + } else { + contact->id = strdup(contact->name); + if (!contact->id) + goto fail; + for (p = 0; contact->id[p]; p++) { + if (isalpha(contact->id[p])) + contact->id[p] = tolower(contact->id[p]); + else + contact->id[p] = '-'; + } + } + } else { + free(contact->id); + snprintf(NULL, 0, "%s-%zu%zn", basenam, num, &r); + contact->id = malloc((size_t)r + 1); + if (!contact->id) + goto fail; + sprintf(contact->id, "%s-%zu", basenam, num); + } + } + + path = libcontacts_get_path(contact->id, user); + if (!path) + goto fail; + + tmppath = alloca(strlen(path) + sizeof("~")); + stpcpy(stpcpy(tmppath, path), "~"); + + fd = open(tmppath, oflags, 0644); + if (fd < 0) { + if ((oflags & O_EXCL) && errno == EEXIST) { + if (!num++) { + basenam = contact->id; + contact->id = NULL; + } + goto generate_id; + } + goto fail; + } + + n = strlen(data); + for (p = 0; p < n; p += (size_t)r) { + r = write(fd, &data[p], n - p); + if (r < 0) + goto fail; + } + + close(fd), fd = -1; + + if (rename(tmppath, path)) + goto fail; + + free(data); + free(path); + free(basenam); + errno = saved_errno; + return 0; + +fail: + saved_errno = errno; + if (fd >= 0) + close(fd); + free(data); + free(path); + free(basenam); + errno = saved_errno; + return -1; +} diff --git a/libcontacts_site_destroy.c b/libcontacts_site_destroy.c new file mode 100644 index 0000000..f1cef31 --- /dev/null +++ b/libcontacts_site_destroy.c @@ -0,0 +1,11 @@ +/* See LICENSE file for copyright and license details. */ +#include "common.h" + + +void +libcontacts_site_destroy(struct libcontacts_site *this) +{ + free(this->context); + free(this->address); + DESTROY_ALL(this->unrecognised_data, free); +} |