From 8b52cc7243c2e0e4ef17db5583999030b8636584 Mon Sep 17 00:00:00 2001 From: Mattias Andrée Date: Fri, 2 Apr 2021 13:17:25 +0200 Subject: First commit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Andrée --- .gitignore | 7 + LICENSE | 15 ++ Makefile | 56 ++++++ TODO | 5 + common.h | 23 +++ config.mk | 8 + libcontacts.h | 247 +++++++++++++++++++++++ libcontacts_address_destroy.c | 15 ++ libcontacts_birthday_destroy.c | 9 + libcontacts_chat_destroy.c | 12 ++ libcontacts_contact_destroy.c | 25 +++ libcontacts_email_destroy.c | 11 + libcontacts_format_contact.c | 197 ++++++++++++++++++ libcontacts_get_path.c | 23 +++ libcontacts_list_contacts.c | 61 ++++++ libcontacts_load_contact.c | 65 ++++++ libcontacts_load_contacts.c | 60 ++++++ libcontacts_number_destroy.c | 11 + libcontacts_organisation_destroy.c | 11 + libcontacts_parse_contact.c | 400 +++++++++++++++++++++++++++++++++++++ libcontacts_pgpkey_destroy.c | 11 + libcontacts_save_contact.c | 93 +++++++++ libcontacts_site_destroy.c | 11 + 23 files changed, 1376 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 TODO create mode 100644 common.h create mode 100644 config.mk create mode 100644 libcontacts.h create mode 100644 libcontacts_address_destroy.c create mode 100644 libcontacts_birthday_destroy.c create mode 100644 libcontacts_chat_destroy.c create mode 100644 libcontacts_contact_destroy.c create mode 100644 libcontacts_email_destroy.c create mode 100644 libcontacts_format_contact.c create mode 100644 libcontacts_get_path.c create mode 100644 libcontacts_list_contacts.c create mode 100644 libcontacts_load_contact.c create mode 100644 libcontacts_load_contacts.c create mode 100644 libcontacts_number_destroy.c create mode 100644 libcontacts_organisation_destroy.c create mode 100644 libcontacts_parse_contact.c create mode 100644 libcontacts_pgpkey_destroy.c create mode 100644 libcontacts_save_contact.c create mode 100644 libcontacts_site_destroy.c 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 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c44b2d9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,15 @@ +ISC License + +© 2021 Mattias Andrée + +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 diff --git a/TODO b/TODO new file mode 100644 index 0000000..f7d9e7a --- /dev/null +++ b/TODO @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include + + +#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 +#include + + +/** + * 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); +} -- cgit v1.2.3-70-g09d2