From d2237bb1d53d5ee870a8ecb2601bc1daad393f3a Mon Sep 17 00:00:00 2001 From: Matt Ranostay Date: Tue, 14 May 2019 00:31:03 -0700 Subject: binding: bluetooth-pbap: add vcard parser framework Parse the vCard information within the binding itself, and reduce service consumers processing and dependencies. Bug-AGL: SPEC-2392 Change-Id: Ie7654c4ba6d269cb8224e61021e19739614afb4e Signed-off-by: Matt Ranostay --- binding/CMakeLists.txt | 1 + binding/bluetooth-pbap-binding.c | 27 +++-- binding/bluetooth-pbap-common.h | 23 ++++ binding/bluetooth-vcard-parser.c | 246 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 283 insertions(+), 14 deletions(-) create mode 100644 binding/bluetooth-pbap-common.h create mode 100644 binding/bluetooth-vcard-parser.c (limited to 'binding') diff --git a/binding/CMakeLists.txt b/binding/CMakeLists.txt index 2477384..3ed0035 100644 --- a/binding/CMakeLists.txt +++ b/binding/CMakeLists.txt @@ -23,6 +23,7 @@ PROJECT_TARGET_ADD(bluetooth-pbap-binding) # Define project Targets add_library(bluetooth-pbap-binding MODULE bluetooth-pbap-binding.c + bluetooth-vcard-parser.c gdbus/freedesktop_dbus_properties_interface.c gdbus/obex_client1_interface.c gdbus/obex_phonebookaccess1_interface.c diff --git a/binding/bluetooth-pbap-binding.c b/binding/bluetooth-pbap-binding.c index 76ee9ad..8dfba3d 100644 --- a/binding/bluetooth-pbap-binding.c +++ b/binding/bluetooth-pbap-binding.c @@ -35,6 +35,8 @@ #include "obex_phonebookaccess1_interface.h" #include "freedesktop_dbus_properties_interface.h" +#include "bluetooth-pbap-common.h" + static GDBusObjectManager *obj_manager; static OrgBluezObexClient1 *client; static OrgBluezObexSession1 *session; @@ -152,10 +154,9 @@ static void on_interface_proxy_properties_changed( } } -static json_object *get_vcard_xfer(gchar *filename) +static char *get_vcard_xfer(gchar *filename) { FILE *fp; - json_object *vcard_str; gchar *vcard_data; size_t size, n; @@ -170,12 +171,10 @@ static json_object *get_vcard_xfer(gchar *filename) return NULL; } - vcard_str = json_object_new_string(vcard_data); - free(vcard_data); fclose(fp); unlink(filename); - return vcard_str; + return vcard_data; } static void get_filename(gchar *filename) @@ -224,8 +223,8 @@ static gchar *pull_vcard(const gchar *handle) static json_object *get_vcard(const gchar *handle) { - json_object *vcard_str = NULL, *vcard = NULL; - gchar *tpath, *filename; + json_object *vcard; + gchar *tpath, *filename, *vcard_str = NULL; tpath = pull_vcard(handle); @@ -240,10 +239,9 @@ static json_object *get_vcard(const gchar *handle) g_free(filename); g_mutex_unlock(&xfer_complete_mutex); - if (vcard_str) { - vcard = json_object_new_object(); - json_object_object_add(vcard, "vcard", vcard_str); - } + vcard = json_object_new_object(); + json_object_object_add(vcard, "vcards", parse_vcards(vcard_str)); + g_free(vcard_str); return vcard; } @@ -276,8 +274,8 @@ static gchar *pull_vcards(int max_entries) static json_object *get_vcards(int max_entries) { - json_object *vcards_str, *vcards; - gchar *tpath, *filename; + json_object *vcards; + gchar *tpath, *filename, *vcards_str = NULL; tpath = pull_vcards(max_entries); g_mutex_lock(&xfer_complete_mutex); @@ -290,7 +288,8 @@ static json_object *get_vcards(int max_entries) g_mutex_unlock(&xfer_complete_mutex); vcards = json_object_new_object(); - json_object_object_add(vcards, "vcards", vcards_str); + json_object_object_add(vcards, "vcards", parse_vcards(vcards_str)); + g_free(vcards_str); return vcards; } diff --git a/binding/bluetooth-pbap-common.h b/binding/bluetooth-pbap-common.h new file mode 100644 index 0000000..8fdbdc7 --- /dev/null +++ b/binding/bluetooth-pbap-common.h @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2019 Konsulko Group + * Author: Matt Ranostay + * + * 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. + */ + +#ifndef BLUETOOTH_PBAP_COMMON_H +#define BLUETOOTH_PBAP_COMMON_H + +json_object *parse_vcards(const char *message); + +#endif /* BLUETOOTH_PBAP_COMMON_H */ 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 + * + * 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 +#include +#include + +#include + +#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; +} -- cgit 1.2.3-korg