[balsa] Add files required by last commit



commit 2b205aafa518661488b3772591cd37e333053e83
Author: Peter Bloomfield <PeterBloomfield bellsouth net>
Date:   Wed Nov 9 23:22:42 2016 -0500

    Add files required by last commit
    
        new file:   libbalsa/address-book-osmo.c
        new file:   libbalsa/address-book-osmo.h
        new file:   libbalsa/rfc6350.c
        new file:   libbalsa/rfc6350.h

 libbalsa/address-book-osmo.c |  355 ++++++++++++++++++++++++++++++++++++++++++
 libbalsa/address-book-osmo.h |   58 +++++++
 libbalsa/rfc6350.c           |  353 +++++++++++++++++++++++++++++++++++++++++
 libbalsa/rfc6350.h           |   45 ++++++
 4 files changed, 811 insertions(+), 0 deletions(-)
---
diff --git a/libbalsa/address-book-osmo.c b/libbalsa/address-book-osmo.c
new file mode 100644
index 0000000..e5028cf
--- /dev/null
+++ b/libbalsa/address-book-osmo.c
@@ -0,0 +1,355 @@
+/* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
+/* Balsa E-Mail Client
+ *
+ * Copyright (C) 1997-2016 Stuart Parmenter and others,
+ *                         See the file AUTHORS for a list.
+ *
+ * Osmo address book support has been written by Copyright (C) 2016
+ * Albrecht Dreß <albrecht dress arcor de>.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Osmo address book
+ *
+ * Add remarks here...
+ */
+#if defined(HAVE_CONFIG_H) && HAVE_CONFIG_H
+# include "config.h"
+#endif                          /* HAVE_CONFIG_H */
+
+#if defined(HAVE_OSMO)
+
+#include <string.h>
+#include <glib.h>
+#include <gio/gio.h>
+#include <glib/gi18n.h>
+
+#include "address-book.h"
+#include "rfc6350.h"
+#include "address-book-osmo.h"
+
+
+/* for the time being, osmo svn rev. 1099 accepts only reading via DBus, not writing new or modified records 
*/
+#undef OSMO_CAN_WRITE
+
+
+#define LOOKUP_MIN_LEN                                 2U
+
+
+static void libbalsa_address_book_osmo_finalize(GObject *object);
+static LibBalsaABErr libbalsa_address_book_osmo_load(LibBalsaAddressBook                *ab,
+                                                     const gchar                                *filter,
+                                                     LibBalsaAddressBookLoadFunc callback,
+                                                     gpointer                                   closure);
+static GList *libbalsa_address_book_osmo_alias_complete(LibBalsaAddressBook *ab,
+                                                        const gchar                    *prefix);
+static GList *osmo_read_addresses(LibBalsaAddressBookOsmo *osmo,
+                                                                 const gchar                     *filter,
+                                                                 GError                                  
**error);
+
+
+G_DEFINE_TYPE(LibBalsaAddressBookOsmo, libbalsa_address_book_osmo, LIBBALSA_TYPE_ADDRESS_BOOK);
+
+
+static void
+libbalsa_address_book_osmo_class_init(LibBalsaAddressBookOsmoClass *klass)
+{
+       LibBalsaAddressBookClass *address_book_class;
+       GObjectClass *object_class;
+
+       object_class = G_OBJECT_CLASS(klass);
+       address_book_class = LIBBALSA_ADDRESS_BOOK_CLASS(klass);
+
+       object_class->finalize = libbalsa_address_book_osmo_finalize;
+
+       address_book_class->load = libbalsa_address_book_osmo_load;
+#if defined(OSMO_CAN_WRITE)
+       address_book_class->add_address = libbalsa_address_book_osmo_add_address;
+       address_book_class->remove_address = libbalsa_address_book_osmo_remove_address;
+       address_book_class->modify_address = libbalsa_address_book_osmo_modify_address;
+#endif
+
+       address_book_class->alias_complete = libbalsa_address_book_osmo_alias_complete;
+}
+
+
+static void
+libbalsa_address_book_osmo_init(LibBalsaAddressBookOsmo *ab)
+{
+       LIBBALSA_ADDRESS_BOOK(ab)->is_expensive = FALSE;
+}
+
+
+static void
+libbalsa_address_book_osmo_finalize(GObject *object)
+{
+       LibBalsaAddressBookOsmo *osmo;
+
+       osmo = LIBBALSA_ADDRESS_BOOK_OSMO(object);
+       if (osmo->proxy != NULL) {
+               g_object_unref(osmo->proxy);
+               osmo->proxy = NULL;
+       }
+
+       G_OBJECT_CLASS(libbalsa_address_book_osmo_parent_class)->finalize(object);
+}
+
+
+LibBalsaAddressBook *
+libbalsa_address_book_osmo_new(const gchar *name)
+{
+       LibBalsaAddressBook *ab = NULL;
+       LibBalsaAddressBookOsmo *osmo;
+
+       osmo = LIBBALSA_ADDRESS_BOOK_OSMO(g_object_new(LIBBALSA_TYPE_ADDRESS_BOOK_OSMO, NULL));
+       ab = LIBBALSA_ADDRESS_BOOK(osmo);
+       ab->name = g_strdup(name);
+
+       return ab;
+}
+
+
+static LibBalsaABErr
+libbalsa_address_book_osmo_load(LibBalsaAddressBook            *ab,
+                                const gchar                            *filter,
+                                LibBalsaAddressBookLoadFunc callback,
+                                gpointer                                       closure)
+{
+       LibBalsaABErr result;
+
+       g_return_val_if_fail (LIBBALSA_IS_ADDRESS_BOOK_OSMO(ab), LBABERR_OK);
+
+       if (callback == NULL) {
+               result = LBABERR_OK;
+       } else {
+               LibBalsaAddressBookOsmo *osmo;
+               GError *error = NULL;
+               GList *addresses;
+
+               osmo = LIBBALSA_ADDRESS_BOOK_OSMO(ab);
+
+               addresses = osmo_read_addresses(osmo, filter, &error);
+               if (error != NULL) {
+                       libbalsa_address_book_set_status(ab, g_strdup_printf(_("Reading Osmo contacts failed: 
%s"), error->message));
+                       g_error_free(error);
+                       result = LBABERR_CANNOT_SEARCH;
+               } else {
+                       GList *this_addr;
+
+                       for (this_addr = addresses; this_addr != NULL; this_addr = this_addr->next) {
+                               callback(ab, LIBBALSA_ADDRESS(this_addr->data), closure);
+                       }
+                       callback(ab, NULL, closure);
+                       g_list_free_full(addresses, g_object_unref);
+                       libbalsa_address_book_set_status(ab, NULL);
+                       result = LBABERR_OK;
+               }
+       }
+
+       return result;
+}
+
+
+/** \brief Utf8-safe strstr
+ *
+ * \param haystack utf8 "haystack" string
+ * \param utf8_needle utf8 "needle" string as returned by g_utf8_casefold()
+ * \return TRUE if needle is found anywhere in haystack
+ */
+static gboolean
+utf8_strstr(const gchar *haystack, const gchar *utf8_needle)
+{
+       gboolean result;
+
+       if (haystack != NULL) {
+               gchar *test;
+
+               test = g_utf8_casefold(haystack, -1);
+               result = (strstr(test, utf8_needle) != NULL);
+               g_free(test);
+       } else {
+               result = FALSE;
+       }
+       return result;
+}
+
+
+/** \brief Check for a pattern in a LibBalsaAddress
+ *
+ * \param address address object
+ * \param utf8_needle utf8 "needle" string as returned by g_utf8_casefold()
+ * \return TRUE if any address string field contains needle
+ *
+ * The fields checked are LibBalsaAddress::full_name, LibBalsaAddress::first_name, 
LibBalsaAddress::last_name,
+ * LibBalsaAddress::nick_name and LibBalsaAddress::organization.
+ */
+static inline gboolean
+utf8_lba_strstr(const LibBalsaAddress *address, const gchar *utf8_needle)
+{
+       return utf8_strstr(address->full_name, utf8_needle) ||
+               utf8_strstr(address->first_name, utf8_needle) ||
+               utf8_strstr(address->last_name, utf8_needle) ||
+               utf8_strstr(address->nick_name, utf8_needle) ||
+               utf8_strstr(address->organization, utf8_needle);
+}
+
+
+static GList *
+libbalsa_address_book_osmo_alias_complete(LibBalsaAddressBook *ab,
+                                          const gchar            *prefix)
+{
+       LibBalsaAddressBookOsmo *osmo;
+       GError *error = NULL;
+       GList *addresses;
+       GList *result = NULL;
+
+       g_return_val_if_fail(LIBBALSA_ADDRESS_BOOK_OSMO(ab), NULL);
+
+       osmo = LIBBALSA_ADDRESS_BOOK_OSMO(ab);
+
+       if (!ab->expand_aliases || strlen(prefix) < LOOKUP_MIN_LEN) {
+               return NULL;
+       }
+
+       g_debug("%s: filter for %s", __func__, prefix);
+       addresses = osmo_read_addresses(osmo, prefix, &error);
+       if (error != NULL) {
+               g_warning("%s: cannot read contacts from Osmo: %s", __func__, error->message);
+               g_error_free(error);
+       } else {
+               GList *p;
+               gchar *utf8_filter;
+
+               utf8_filter = g_utf8_casefold(prefix, -1);
+               for (p = addresses; p != NULL; p = p->next) {
+                       LibBalsaAddress *this_addr = LIBBALSA_ADDRESS(p->data);
+                       GList *this_mail;
+                       gboolean names_match;
+
+                       names_match = utf8_lba_strstr(this_addr, utf8_filter);
+                       for (this_mail = this_addr->address_list; this_mail != NULL; this_mail = 
this_mail->next) {
+                               const gchar *mail_addr = (gchar *) this_mail->data;
+
+                               if (names_match || (strstr(mail_addr, prefix) != NULL)) {
+                                       InternetAddress *addr;
+
+                                       g_debug("%s: found %s <%s>", __func__, this_addr->full_name, 
mail_addr);
+                                       addr = internet_address_mailbox_new(this_addr->full_name, 
g_strdup(mail_addr));
+                                       result = g_list_prepend(result, g_object_ref(addr));
+                               }
+                       }
+               }
+               g_free(utf8_filter);
+               g_list_free_full(addresses, g_object_unref);
+
+               if (result != NULL) {
+                       result = g_list_reverse(result);
+               }
+       }
+
+       return result;
+}
+
+
+/** \brief Read filtered addresses from Osmo via DBus
+ *
+ * \param osmo Osmo address book object
+ * \param filter search filter, NULL or "" for all entries
+ * \param error filled with error information on error
+ * \return a list \ref LibBalsaAddress items on success or NULL on error or if no item matches the search 
filter
+ *
+ * Create the proxy LibBalsaAddressBookOsmo::proxy if required, and ask Osmo for addresses.  Only items with 
any mail address are
+ * added to the returned list.  The caller can distinguish between an error and an empty query result by 
checking if error is not
+ * NULL.
+ *
+ * \note The caller must free the returned list.
+ */
+static GList *
+osmo_read_addresses(LibBalsaAddressBookOsmo *osmo,
+                                       const gchar                             *filter,
+                                       GError                                  **error)
+{
+       GList *addresses = NULL;
+
+       /* connect to DBus unless we already have a proxy */
+       if (osmo->proxy == NULL) {
+               osmo->proxy =
+                       g_dbus_proxy_new_for_bus_sync(G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, NULL, 
"org.clayo.osmo.Contacts",
+                                                                                 "/org/clayo/osmo/Contacts", 
"org.clayo.osmo.Contacts", NULL, error);
+       }
+
+       /* proceed only if we have the proxy */
+       if (osmo->proxy != NULL) {
+               GVariant *request;
+               GVariant *reply;
+
+               if (filter != NULL) {
+                       request = g_variant_new("(s)", filter);
+               } else {
+                       request = g_variant_new("(s)", "");
+               }
+               reply = g_dbus_proxy_call_sync(osmo->proxy, "Find", request, G_DBUS_CALL_FLAGS_NONE, -1, 
NULL, error);
+
+               /* proceed only if we got a reply */
+               if (reply != NULL) {
+                       gchar *vcards;
+                       GInputStream *stream;
+                       GDataInputStream *data;
+                       gboolean eos;
+
+                       /* create a stream from the VCards */
+                       g_variant_get(reply, "(s)", &vcards);
+                       stream = g_memory_input_stream_new_from_data(vcards, -1, NULL);
+                       data = g_data_input_stream_new(stream);
+                       g_data_input_stream_set_newline_type(data, G_DATA_STREAM_NEWLINE_TYPE_CR_LF);
+
+                       /* decode all returned VCard's, skip those without email addresses */
+                       eos = FALSE;
+                       do {
+                               LibBalsaAddress *this_addr;
+
+                               this_addr = rfc6350_parse_from_stream(data, &eos, error);
+                               if (this_addr != NULL) {
+                                       if (this_addr->address_list != NULL) {
+                                               addresses = g_list_prepend(addresses, this_addr);
+                                       } else {
+                                               g_object_unref(G_OBJECT(this_addr));
+                                       }
+                               }
+                       } while (!eos && (*error == NULL));
+
+                       /* clean up */
+                       g_object_unref(G_OBJECT(data));
+                       g_object_unref(G_OBJECT(stream));
+                       g_free(vcards);
+                       g_variant_unref(reply);
+
+                       /* drop list on error, reverse order otherwise */
+                       if (addresses != NULL) {
+                               if (*error != NULL) {
+                                       g_list_free_full(addresses, g_object_unref);
+                                       addresses = NULL;
+                               } else {
+                                       addresses = g_list_reverse(addresses);
+                               }
+                       }
+               }
+       }
+
+       return addresses;
+}
+
+#endif /* HAVE_OSMO */
diff --git a/libbalsa/address-book-osmo.h b/libbalsa/address-book-osmo.h
new file mode 100644
index 0000000..0a020b2
--- /dev/null
+++ b/libbalsa/address-book-osmo.h
@@ -0,0 +1,58 @@
+/* -*-mode:c; c-style:k&r; c-basic-offset:4; -*- */
+/* Balsa E-Mail Client
+ *
+ * Copyright (C) 1997-2016 Stuart Parmenter and others,
+ *                         See the file AUTHORS for a list.
+ *
+ * Osmo address book support has been written by Copyright (C) 2016
+ * Albrecht Dreß <albrecht dress arcor de>.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+/*
+ * Access osmo (http://clayo.org/osmo/) address book through DBus
+ */
+
+#ifndef LIBBALSA_ADDRESS_BOOK_OSMO_H__
+#define LIBBALSA_ADDRESS_BOOK_OSMO_H__
+
+#include <gio/gio.h>
+#include "address-book.h"
+
+#define LIBBALSA_TYPE_ADDRESS_BOOK_OSMO                (libbalsa_address_book_osmo_get_type())
+#define LIBBALSA_ADDRESS_BOOK_OSMO(obj)                (G_TYPE_CHECK_INSTANCE_CAST(obj, 
LIBBALSA_TYPE_ADDRESS_BOOK_OSMO, LibBalsaAddressBookOsmo))
+#define LIBBALSA_ADDRESS_BOOK_OSMO_CLASS(klass)        (G_TYPE_CHECK_CLASS_CAST(klass, 
LIBBALSA_TYPE_ADDRESS_BOOK_OSMO, LibBalsaAddressBookOsmoClass))
+#define LIBBALSA_IS_ADDRESS_BOOK_OSMO(obj)             (G_TYPE_CHECK_INSTANCE_TYPE(obj, 
LIBBALSA_TYPE_ADDRESS_BOOK_OSMO))
+#define LIBBALSA_IS_ADDRESS_BOOK_OSMO_CLASS(klass)  (G_TYPE_CHECK_CLASS_TYPE(klass, 
LIBBALSA_TYPE_ADDRESS_BOOK_OSMO))
+
+typedef struct _LibBalsaAddressBookOsmo LibBalsaAddressBookOsmo;
+typedef struct _LibBalsaAddressBookOsmoClass LibBalsaAddressBookOsmoClass;
+
+struct _LibBalsaAddressBookOsmo {
+       LibBalsaAddressBook parent;
+
+       GDBusProxy *proxy;
+};
+
+struct _LibBalsaAddressBookOsmoClass {
+       LibBalsaAddressBookClass parent_class;
+};
+
+GType libbalsa_address_book_osmo_get_type(void);
+
+LibBalsaAddressBook *libbalsa_address_book_osmo_new(const gchar *name);
+
+
+#endif              /* __LIBBALSA_ADDRESS_BOOK_LDAP_H__ */
diff --git a/libbalsa/rfc6350.c b/libbalsa/rfc6350.c
new file mode 100644
index 0000000..0c902c0
--- /dev/null
+++ b/libbalsa/rfc6350.c
@@ -0,0 +1,353 @@
+/* Balsa E-Mail Client
+ *
+ * Copyright (C) 1997-2016 Stuart Parmenter and others, see the file AUTHORS for a list.
+ *
+ * This module provides a simple RFC 6350 (aka VCard 4.0, see https://tools.ietf.org/html/rfc6350) parser 
which extracts a single
+ * VCard from a GDataInputStream and returns it as LibBalsaAddress.
+ *
+ * Written by Copyright (C) 2016 Albrecht Dreß <albrecht dress arcor de>.
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the GNU 
General Public License as
+ * published by the Free Software Foundation; either version 2, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even 
the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#if defined(HAVE_CONFIG_H) && HAVE_CONFIG_H
+# include "config.h"
+#endif                          /* HAVE_CONFIG_H */
+
+#if defined(HAVE_OSMO)
+
+#include <glib/gi18n.h>
+#include "rfc6350.h"
+
+
+/* define GError stuff for the RFC 6350/VCard parser */
+#define RFC6350_ERROR_QUARK                            (g_quark_from_static_string("rfc6350-parser"))
+#define RFC6350_ERROR_NO_COLON                 1
+#define RFC6350_ERROR_EMPTY                            2
+#define RFC6350_ERROR_BEGIN                            3
+#define RFC6350_ERROR_END                              4
+
+
+static gboolean rfc6350_eval_line(gchar                          *line,
+                                                                 LibBalsaAddress *address,
+                                                                 GError                  **error);
+static gchar **rfc6350_strsplit(const gchar *item,
+                                                               guint       count);
+static gchar *rfc6350_get_name(gchar *name);
+static void rfc6350_unescape(gchar *item);
+static gchar *rfc6350_fn_from_n(gchar **n_items);
+
+
+LibBalsaAddress *
+rfc6350_parse_from_stream(GDataInputStream *stream,
+                                                 gboolean                 *eos,
+                                                 GError                   **error)
+{
+       gchar *line;
+       LibBalsaAddress *result = NULL;
+
+       line = g_data_input_stream_read_line_utf8(stream, NULL, NULL, error);
+       if (line == NULL) {
+               if (*error == NULL) {
+                       *eos = TRUE;
+               }
+       } else if (g_ascii_strcasecmp(line, "BEGIN:VCARD") != 0) {
+               g_set_error(error, RFC6350_ERROR_QUARK, RFC6350_ERROR_BEGIN, _("malformed card, BEGIN:VCARD 
expected"));
+               g_free(line);
+       } else {
+               gboolean parse_done = FALSE;
+
+               result = libbalsa_address_new();
+               while (result && (line != NULL) && !parse_done) {
+                       if (g_ascii_strcasecmp(line, "END:VCARD") == 0) {
+                               parse_done = TRUE;
+                               g_free(line);
+                       } else {
+                               gchar *nextline;
+
+                               /* perform unfolding (RFC 6350, sect. 3.2. "Line Delimiting and Folding") */
+                               nextline = g_data_input_stream_read_line_utf8(stream, NULL, NULL, error);
+                               while ((nextline) != NULL && ((nextline[0] == ' ') || (nextline[0] == '\t'))) 
{
+                                       gchar *unfold;
+
+                                       unfold = g_strconcat(line, &nextline[1], NULL);
+                                       g_free(line);
+                                       g_free(nextline);
+                                       line = unfold;
+                                       nextline = g_data_input_stream_read_line_utf8(stream, NULL, NULL, 
error);
+                               }
+
+                               /* evaluate unfolded line, drop address on error */
+                               if (!rfc6350_eval_line(line, result, error)) {
+                                       g_object_unref(result);
+                                       result = NULL;
+                               }
+
+                               /* process next line */
+                               g_free(line);
+                               line = nextline;
+                       }
+               }
+
+               if (!parse_done) {
+                       g_set_error(error, RFC6350_ERROR_QUARK, RFC6350_ERROR_END, _("malformed card, 
END:VCARD missing"));
+                       g_object_unref(result);
+                       result = NULL;
+               }
+       }
+
+       /* ignore items without an Email address, fill empty full name if necessary */
+       if (result != NULL) {
+               if (result->address_list == NULL) {
+                       g_object_unref(result);
+                       result = NULL;
+               } else if (result->full_name == NULL) {
+                       result->full_name = g_strdup(_("No-Name"));
+               }
+       }
+
+       return result;
+}
+
+
+/** \brief Extract a VCard name item
+ *
+ * \param name input name field, modified in place
+ * \return the name field, with group and parameters stripped
+ *
+ * Remove the \em group and \em param parts of the VCard \em name field (see RFC 6350, sect. 3.3. "ABNF 
Format Definition").
+ *
+ * \note Do \em not free the returned value.
+ */
+static gchar *
+rfc6350_get_name(gchar *name)
+{
+       gchar *result;
+       gchar *semicolon;
+
+       /* skip group */
+       result = strchr(name, '.');
+       if (result == NULL) {
+               result = name;
+       } else {
+               result = &result[1];
+       }
+
+       /* drop any name parameters */
+       semicolon = strchr(result, ';');
+       if (semicolon != NULL) {
+               semicolon[0] = '\0';
+       }
+
+       return result;
+}
+
+
+/** \brief RFC 6350 unescape a string
+ *
+ * \param item VCard item, modified in place
+ *
+ * Unescape a string according to RFC 6350, sect. 3.4. "Property Value Escaping".  Note that all other 
escaped characters than those
+ * defined in RFC 6350 are simply ignored, although they \em should be regarded as errors.
+ *
+ * \note Do \em not free the returned value.
+ */
+static void
+rfc6350_unescape(gchar *item)
+{
+       gchar *p;
+       gchar *bslash;
+
+       g_assert(item != NULL);
+
+       p = item;
+       do {
+               bslash = strchr(p, '\\');
+               if (bslash != NULL) {
+                       if (strchr(",;\\nN", bslash[1]) != NULL) {
+                               if (g_ascii_tolower(bslash[1]) == 'n') {
+                                       bslash[1] = '\n';
+                               }
+                               memmove(bslash, &bslash[1], strlen(bslash));
+                       }
+                       p = &bslash[1];
+               } else {
+                       p = NULL;
+               }
+       } while (p != NULL);
+}
+
+
+/** \brief Split a ';' delimited list into items
+ *
+ * \param item value string consisting of ';' delimited items
+ * \param count maximum number of fields to split into
+ * \return a newly allocated NULL-terminated array of unescaped fields
+ *
+ * Split the passed value string into fields, and unescape the resulting values.  If the \em item contains 
more than \em count
+ * fields, the extra delimiters are ignored.
+ *
+ * \note Free the returned value by calling g_strfreev() on it.
+ */
+static gchar **
+rfc6350_strsplit(const gchar *item,
+                                guint           count)
+{
+       gchar **result;
+       const gchar *start;
+       guint index;
+
+       result = g_new0(gchar *, count + 1U);
+       start = item;
+       index = 0U;
+
+       while ((start != NULL) && (index < count)) {
+               if (start[0] == ';') {
+                       result[index] = g_strdup("");
+                       start = &start[1];
+               } else {
+                       const gchar *delim;
+
+                       delim = strchr(start, ';');
+                       while ((delim != NULL) && (delim[-1] == '\\')) {
+                               delim = strchr(&delim[1], ';');
+                       }
+                       if (delim != NULL) {
+                               result[index] = g_strndup(start, delim - start);
+                               start = &delim[1];
+                       } else {
+                               result[index] = g_strdup(start);
+                               start = NULL;
+                       }
+                       rfc6350_unescape(result[index]);
+               }
+               index++;
+       }
+
+       return result;
+}
+
+
+/** \brief Evaluate a VCard line
+ *
+ * \param line VCard line
+ * \param address target address object
+ * \param error filled with error information on error
+ * \return TRUE on success, or FALSE is the VCard line is malformed
+ *
+ * Evaluate the VCard line, extract a N, FN, NICKNAME, ORG or EMAIL item and assign it to the appropriate 
fields in the target
+ * address item.
+ */
+static gboolean
+rfc6350_eval_line(gchar                          *line,
+                                 LibBalsaAddress *address,
+                                 GError                  **error)
+{
+       gchar *value;
+       gboolean result;
+
+       /* split into name and value */
+       value = strchr(line, ':');
+       if (value == NULL) {
+               g_set_error(error, RFC6350_ERROR_QUARK, RFC6350_ERROR_NO_COLON, _("malformed line '%s', 
missing ':'"), line);
+               result = FALSE;
+       } else {
+               gchar *namepart;
+               gchar *name;
+
+               /* get the name and make sure that neither name nor value are empty */
+               namepart = g_strndup(line, value - line);
+               name = rfc6350_get_name(namepart);
+               value = &value[1];
+               if ((name[0] == '\0') || (value[0] == '\0')) {
+                       g_set_error(error, RFC6350_ERROR_QUARK, RFC6350_ERROR_EMPTY, _("malformed line '%s', 
empty name or value"), line);
+                       result = FALSE;
+               } else {
+                       g_debug("%s: line='%s' name='%s', value='%s'", __func__, line, name, value);
+                       if (g_ascii_strcasecmp(name, "FN") == 0) {
+                               rfc6350_unescape(value);
+                               g_free(address->full_name);
+                               address->full_name = g_strdup(value);
+                       } else if (g_ascii_strcasecmp(name, "N") == 0) {
+                               gchar **n_items;
+
+                               n_items = rfc6350_strsplit(value, 5U);
+                               g_free(address->first_name);
+                               g_free(address->last_name);
+                               if (n_items[1] != NULL) {
+                                       address->first_name = g_strdup(n_items[1]);
+                               } else {
+                                       address->first_name = NULL;
+                               }
+                               if (n_items[0] != NULL) {
+                                       address->last_name = g_strdup(n_items[0]);
+                               } else {
+                                       address->last_name = NULL;
+                               }
+                               if (address->full_name == NULL) {
+                                       address->full_name = rfc6350_fn_from_n(n_items);
+                               }
+                               g_strfreev(n_items);
+                       } else if (g_ascii_strcasecmp(name, "NICKNAME") == 0) {
+                               rfc6350_unescape(value);
+                               g_free(address->nick_name);
+                               address->nick_name = g_strdup(value);
+                       } else if (g_ascii_strcasecmp(name, "ORG") == 0) {
+                               gchar **n_items;
+
+                               n_items = rfc6350_strsplit(value, 2U);
+                               g_free(address->organization);
+                               address->organization = g_strdup(n_items[0]);
+                               g_strfreev(n_items);
+                       } else if (g_ascii_strcasecmp(name, "EMAIL") == 0) {
+                               rfc6350_unescape(value);
+                               address->address_list = g_list_prepend(address->address_list, 
g_strdup(value));
+                       } else {
+                               /* ignore any other items */
+                       }
+               }
+               g_free(namepart);
+               result = TRUE;
+       }
+
+       return result;
+}
+
+
+/** \brief Create a full name from VCard "N" items
+ *
+ * \param n_items extracted VCard N items
+ * \return the full name
+ *
+ * Construct a full name by concatenating -in this order- the VCard "N" elements Honorific Prefixes, Given 
Names, Additional Names,
+ * Family Names and Honorific Suffixes, separated by a single space each.
+ */
+static gchar *
+rfc6350_fn_from_n(gchar **n_items)
+{
+       GString *fn;
+       static const guint add_idx[5] = { 3U, 1U, 2U, 0U, 4U };
+       guint n;
+
+       fn = g_string_new(NULL);
+
+       for (n = 0; n < 5U; n++) {
+               if ((n_items[add_idx[n]] != NULL) && (n_items[add_idx[n]][0] != '\0')) {
+                       if (fn->len > 0U) {
+                               fn = g_string_append_c(fn, ' ');
+                       }
+                       fn = g_string_append(fn, n_items[add_idx[n]]);
+               }
+       }
+
+       return g_string_free(fn, FALSE);
+}
+
+#endif /* HAVE_OSMO */
diff --git a/libbalsa/rfc6350.h b/libbalsa/rfc6350.h
new file mode 100644
index 0000000..4904ef1
--- /dev/null
+++ b/libbalsa/rfc6350.h
@@ -0,0 +1,45 @@
+/* Balsa E-Mail Client
+ *
+ * Copyright (C) 1997-2016 Stuart Parmenter and others, see the file AUTHORS for a list.
+ *
+ * This module provides a simple RFC 6350 (aka VCard 4.0, see https://tools.ietf.org/html/rfc6350) parser 
which extracts a single
+ * VCard from a GDataInputStream and returns it as LibBalsaAddress.
+ *
+ * Written by Copyright (C) 2016 Albrecht Dreß <albrecht dress arcor de>.
+ *
+ * This program is free software; you can redistribute it and/or modify it under the terms of the GNU 
General Public License as
+ * published by the Free Software Foundation; either version 2, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even 
the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along with this program; if not, see
+ * <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef LIBBALSA_RFC6350_H_
+#define LIBBALSA_RFC6350_H_
+
+
+#include "address.h"
+
+
+/** \brief Extract a single VCard 4.0
+ *
+ * \param stream VCard input stream
+ * \param eos filled with TRUE is the end of the input stream has been reached
+ * \param error filled with error information on error
+ * \return a new address object on success
+ *
+ * Read a VCard from the input stream and extract all relevant fields into a LibBalsaAddress.  A 
LibBalsaAddress is returned only if
+ * it contains any email address.  The caller shall distinguish this case from an error by checking if the 
error argument is filled.
+ * The input stream is positioned immediately after the card read.
+ *
+ * \note The caller shall unref the returned address object.
+ */
+LibBalsaAddress *rfc6350_parse_from_stream(GDataInputStream *stream,
+                                                                                  gboolean                   
  *eos,
+                                                                                  GError                     
  **error);
+
+
+#endif /* LIBBALSA_RFC6350_H_ */


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]