aboutsummaryrefslogtreecommitdiffstats
path: root/binding/bluetooth-vcard-parser.c
diff options
context:
space:
mode:
Diffstat (limited to 'binding/bluetooth-vcard-parser.c')
-rw-r--r--binding/bluetooth-vcard-parser.c246
1 files changed, 246 insertions, 0 deletions
diff --git a/binding/bluetooth-vcard-parser.c b/binding/bluetooth-vcard-parser.c
new file mode 100644
index 0000000..927443a
--- /dev/null
+++ b/binding/bluetooth-vcard-parser.c
@@ -0,0 +1,246 @@
+/*
+ * Copyright (C) 2019 Konsulko Group
+ * Author: Matt Ranostay <matt.ranostay@konsulko.com>
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <string.h>
+#include <glib.h>
+
+#include <json-c/json.h>
+
+#define CRLF "\r\n"
+#define SUPPORTED_VERSION "3.0"
+
+enum {
+ VC_FORMATTED_NAME,
+ VC_TELEPHONE,
+ VC_DATETIME,
+ VC_PHOTO,
+ VC_FIELD_COUNT,
+};
+
+static const gchar *VC_PREFIXES[VC_FIELD_COUNT] = {
+ "FN",
+ "TEL",
+ "X-IRMC-CALL-DATETIME",
+ "PHOTO",
+};
+
+#define vc_len(name) strlen(VC_PREFIXES[name])
+
+#define vc_strstr(ptr, name) \
+ ({ \
+ void *_ptr = strstr(ptr, name); \
+ if (_ptr) _ptr += strlen(name); \
+ _ptr; \
+ })
+
+static void add_fn(json_object *resp, gchar *msg)
+{
+ gchar *name = msg + vc_len(VC_FORMATTED_NAME) + 1;
+
+ if (!name)
+ name = "UNKNOWN";
+
+ json_object_object_add(resp, "fn", json_object_new_string(name));
+}
+
+static void add_phone(json_object *array, gchar *msg)
+{
+ gchar *ptr = strstr(msg, ":");
+ json_object *dict, *number;
+
+ if (!ptr || !strlen(ptr + 1))
+ return;
+
+ number = json_object_new_string(ptr + 1);
+
+ ptr = vc_strstr(msg, "TYPE=");
+ if (!ptr)
+ ptr = "UNKNOWN";
+ else
+ ptr = strsep(&ptr, ",;:");
+
+ dict = json_object_new_object();
+ json_object_object_add(dict, ptr, number);
+ json_object_array_add(array, dict);
+}
+
+static void add_datetime(json_object *resp, gchar *msg)
+{
+ gchar *ptr;
+
+ msg += vc_len(VC_DATETIME) + 1;
+
+ ptr = strsep(&msg, ":");
+ if (!ptr)
+ return;
+ json_object_object_add(resp, "type", json_object_new_string(ptr));
+
+ ptr = strsep(&msg, "");
+ if (!ptr)
+ return;
+ json_object_object_add(resp, "timestamp", json_object_new_string(ptr));
+}
+
+static gchar **extract_photo(json_object *resp, gchar **msg)
+{
+ GString *data;
+ gchar *ptr;
+ json_object *obj;
+
+ // TODO: handle other mimetypes
+ if (!strstr(*msg, "TYPE=JPEG"))
+ return msg;
+
+ ptr = strstr(*msg, ":");
+ if (!ptr)
+ return msg;
+
+ data = g_string_new(NULL);
+ g_string_append(data, ptr + 1);
+
+ if (!*msg++)
+ return msg;
+
+ for (; *msg; msg++) {
+ if (!strlen(*msg))
+ break;
+ if (!g_ascii_isspace(**msg))
+ break;
+ g_string_append(data, g_strchug(*msg));
+ }
+
+ obj = json_object_new_object();
+ json_object_object_add(obj, "mimetype", json_object_new_string("image/jpeg"));
+ json_object_object_add(obj, "data", json_object_new_string(data->str));
+ json_object_object_add(resp, "photo", obj);
+
+ g_string_free(data, TRUE);
+
+ return msg;
+}
+
+static int tag_index(gchar *msg)
+{
+ gchar *ptr;
+ int i;
+
+ for (i = 0; i < VC_FIELD_COUNT; i++) {
+ if (!g_str_has_prefix(msg, VC_PREFIXES[i]))
+ continue;
+
+ ptr = vc_strstr(msg, VC_PREFIXES[i]);
+ if (*ptr == ':' || *ptr == ';')
+ return i;
+
+ break;
+ }
+
+ return -EINVAL;
+}
+
+static void process_vcard(json_object *resp, gchar **start, gchar **end)
+{
+ json_object *vcard = json_object_new_object();
+ json_object *phone = NULL;
+ gboolean history = FALSE;
+
+ for (; start != end; start++) {
+ int idx = tag_index(*start);
+
+ switch (idx) {
+ case VC_FORMATTED_NAME:
+ add_fn(vcard, *start);
+ break;
+ case VC_TELEPHONE:
+ if (!phone)
+ phone = json_object_new_array();
+ add_phone(phone, *start);
+ break;
+ case VC_DATETIME:
+ history = TRUE;
+ add_datetime(vcard, *start);
+ break;
+ case VC_PHOTO:
+ start = extract_photo(vcard, start);
+ break;
+ default:
+ continue;
+ }
+ }
+
+ if (phone) {
+ if (!json_object_array_length(phone)) {
+ // No phone numbers, so discard
+ json_object_put(phone);
+ json_object_put(vcard);
+ return;
+ }
+
+ if (!history)
+ json_object_object_add(vcard, "telephone", phone);
+ else {
+ json_object *number = json_object_array_get_idx(phone, 0);
+
+ json_object_object_foreach(number, unused, val) {
+ json_object_object_add(vcard, "telephone", val);
+ (void) unused;
+ }
+ }
+ }
+ json_object_array_add(resp, vcard);
+}
+
+json_object *parse_vcards(const char *message)
+{
+ gchar **lines, **tmp, **ptr = NULL;
+ json_object *resp;
+
+ if (!message || !strlen(message))
+ return NULL;
+
+ resp = json_object_new_array();
+ lines = g_strsplit(message, CRLF, -1);
+
+ for (tmp = lines; *tmp; tmp++) {
+ // BEGIN:VCARD
+ if (!g_strcmp0(*tmp, "BEGIN:VCARD")) {
+ ptr = tmp + 1;
+ continue;
+ }
+
+ if (!ptr)
+ continue;
+
+ // VERSION should be the line after BEGIN:VCARD
+ if (tmp == ptr && g_strcmp0(*tmp, "VERSION:" SUPPORTED_VERSION)) {
+ ptr = NULL;
+ continue;
+ }
+
+ // END:VCARD
+ if (!g_strcmp0(*tmp, "END:VCARD")) {
+ process_vcard(resp, ptr, tmp);
+ ptr = NULL;
+ }
+ }
+
+ g_strfreev(lines);
+
+ return resp;
+}