diff options
-rw-r--r-- | README.md | 68 | ||||
-rw-r--r-- | binding/CMakeLists.txt | 1 | ||||
-rw-r--r-- | binding/bluetooth-pbap-binding.c | 27 | ||||
-rw-r--r-- | binding/bluetooth-pbap-common.h | 23 | ||||
-rw-r--r-- | binding/bluetooth-vcard-parser.c | 246 |
5 files changed, 332 insertions, 33 deletions
@@ -21,9 +21,24 @@ Bluetooth PBAP service reports respective vCard phonebook data from BlueZ via co Returns all vCards that are accessible from respective connected device in concatenated output: <pre> - "response": { - "vcards": "BEGIN:VCARD\r\nVERSION:3.0\r\nFN:Art McGee\r\nN:Art\r\nTEL: +13305551212\r\nUID:27e\r\nEND:VCARD\r\n" - } + +"response": { + "vcards": [ + { + "fn": "Art McGee", + "photo": { + "mimetype": "image/jpeg", + "data": 'BASE64 IMAGE BLOB HERE' + }, + "telephone": [ + { + "CELL": "+13305551212" + } + ] + }, + ... + ] +} </pre> ### search Verb @@ -31,14 +46,14 @@ Returns all vCards that are accessible from respective connected device in conca Example of a request for vCard search using **number** parameter (i.e. *{"number":"+15035551212"}*) results: <pre> - "response": { - "results": [ - { - "handle": "27e.vcf", - "name": "Art McGee" - } - ] - } }, +"response": { + "results": [ + { + "handle": "27e.vcf", + "name": "Art McGee" + } + ] +} }, </pre> ### entry Verb @@ -55,11 +70,7 @@ Client must pass one of the following values to the **list** parameter in reques Also there is a **handle** parameter that must be in form of vCard path (e.g. 27e.vcf). -<pre> - "response": { - "vcard":"BEGIN:VCARD\r\nVERSION:3.0\r\nFN:Art McGee\r\nN:;Art\r\nTEL;TYPE=CELL:+13305551212\r\nUID:27e\r\nEND:VCARD\r\n" - } -</pre> +Response is the same as noted in the **contacts** verb ### history Verb @@ -75,9 +86,28 @@ Client must pass one of the following values to the list parameter in request: Sample request for a combined list (i.e. *{"list":"cch"}*) and its respective response: <pre> - "response": { - "vcards":"BEGIN:VCARD\r\nVERSION:3.0\r\nFN:Art \r\nN:\r\nTEL:3305551212\r\nX-IRMC-CALL-DATETIME;DIALED:20190103T202524\r\nEND:VCARD\r\nBEGIN:VCARD\r\nVERSION:3.0\r\nFN:Art McGee\r\nN:;Art\r\nTEL;TYPE=CELL:+15035551212\r\nUID:27e\r\nX-IRMC-CALL-DATETIME;RECEIVED:20181207T065311\r\nEND:VCARD\r\nBEGIN:VCARD\r\nVERSION:3.0\r\n" - } +"response": { + "vcards": [ + { + "fn": "Art McGee" + "type": "DIALED", + "timestamp": "20190509T193422", + "telephone": "+13305551212" + }, + { + "fn": "UNKNOWN CALLER", + "type": "MISSED", + "timestamp": "20190426T014109", + "telephone": "+15035551212" + }, + { + "fn": "Satoshi Nakamoto" + "type": "RECEIVED", + "timestamp": "20190421T090123", + "telephone": "+13605551212" + } + ] +} </pre> ## Events 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 <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. + */ + +#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 <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; +} |