aboutsummaryrefslogtreecommitdiffstats
path: root/binding/bluetooth-vcard-parser.c
blob: 0b63209addf3ac1c241cdbaa298e79011a551c85 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
SUMMARY     = "AGL demo control panel"
LICENSE     = "Apache-2.0"
LIC_FILES_CHKSUM = "file://LICENSE;md5=685e0faaaec2c2334cf8159ca6bd2975"

PV = "1.0+git${SRCPV}"

SRC_URI = "git://gerrit.automotivelinux.org/gerrit/src/agl-demo-control-panel;protocol=htt
/*
 * 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 = strstr(msg, ":");

	if (!name || !strlen(name + 1))
		return;

	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;
}