diff options
Diffstat (limited to 'binding/bluetooth-vcard-parser.c')
-rw-r--r-- | binding/bluetooth-vcard-parser.c | 246 |
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; +} |