/* * 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 = 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; }