aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore7
-rw-r--r--LICENSE15
-rw-r--r--Makefile56
-rw-r--r--TODO5
-rw-r--r--common.h23
-rw-r--r--config.mk8
-rw-r--r--libcontacts.h247
-rw-r--r--libcontacts_address_destroy.c15
-rw-r--r--libcontacts_birthday_destroy.c9
-rw-r--r--libcontacts_chat_destroy.c12
-rw-r--r--libcontacts_contact_destroy.c25
-rw-r--r--libcontacts_email_destroy.c11
-rw-r--r--libcontacts_format_contact.c197
-rw-r--r--libcontacts_get_path.c23
-rw-r--r--libcontacts_list_contacts.c61
-rw-r--r--libcontacts_load_contact.c65
-rw-r--r--libcontacts_load_contacts.c60
-rw-r--r--libcontacts_number_destroy.c11
-rw-r--r--libcontacts_organisation_destroy.c11
-rw-r--r--libcontacts_parse_contact.c400
-rw-r--r--libcontacts_pgpkey_destroy.c11
-rw-r--r--libcontacts_save_contact.c93
-rw-r--r--libcontacts_site_destroy.c11
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
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 <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
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 <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);
+}