/* * 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 #include #include #include #include #include #define AFB_BINDING_VERSION 3 #include static void populate_json_entry(gchar *msg, json_object *jresp) { gchar **tmp = g_strsplit(msg, ":", 2); gchar *key = g_ascii_strdown(tmp[0], -1), *ptr; // TODO: process CHARSET in fields ptr = strstr(key, ";"); if (ptr) *ptr = '\0'; if (strlen(tmp[1])) json_object_object_add(jresp, key, json_object_new_string(tmp[1])); g_free(key); g_strfreev(tmp); } static gboolean __expect(gchar ***msg, gchar *str, json_object *jresp, gboolean autoincrement) { gboolean ret = FALSE; gchar *tmp; if (!**msg) return FALSE; tmp = **msg; if (jresp) { ret = g_str_has_prefix(tmp, str); populate_json_entry(tmp, jresp); } else if (g_str_has_suffix(str, ":")) { ret = g_str_has_prefix(tmp, str); } else { if (!g_strcmp0(tmp, str)) ret = TRUE; } if (autoincrement) (*msg)++; return ret; } #define expect(msg, str, jresp) { if (!__expect(msg, str, jresp, TRUE)) { return FALSE; }; } static gboolean parse_recipient_vcard(gchar ***msg, json_object *jresp) { json_object *jobj; expect(msg, "VERSION:", NULL); jobj = json_object_new_object(); json_object_object_add(jresp, "recipient", jobj); expect(msg, "FN", jobj); expect(msg, "N", jobj); expect(msg, "TEL:", jobj); return TRUE; } static gboolean __bmessage_parse(gchar **msg, json_object *jresp) { gboolean benv = FALSE, bmsg = FALSE, vcard = FALSE; GString *message = g_string_new(NULL); // BMSG: Begin of valid message expect(&msg, "BEGIN:BMSG", NULL); // BMSG: Both Android and iOS expect(&msg, "VERSION:", NULL); expect(&msg, "STATUS:", jresp); expect(&msg, "TYPE:", jresp); expect(&msg, "FOLDER:", jresp); // BMSG: iOS puts some non-standard stuff here while (*msg) { if (!vcard && __expect(&msg, "BEGIN:VCARD", NULL, TRUE)) { vcard = parse_recipient_vcard(&msg, jresp); continue; } // VCARD: any BENV is invalid if comes before the recipient data if (!vcard) continue; if (!benv && __expect(&msg, "BEGIN:BENV", NULL, TRUE)) { benv = TRUE; continue; } // BENV: can't be in the message body yet if (!benv) continue; if (!bmsg && __expect(&msg, "BEGIN:MSG", NULL, TRUE)) { bmsg = TRUE; continue; } // MSG: not in message yet if (!bmsg) continue; // MSG: Keep looking for END:MSG if (!__expect(&msg, "END:MSG", NULL, FALSE)) { g_string_append(message, *msg); g_string_append(message, "\n"); } // MSG: Keep looking for END:MSG (increment this time) if (__expect(&msg, "END:MSG", NULL, TRUE)) break; } // BMSG: End of valid message expect(&msg, "END:BBODY", NULL); expect(&msg, "END:BENV", NULL); expect(&msg, "END:BMSG", NULL); // remove trailing \n message->str[message->len - 1] = '\0'; json_object_object_add(jresp, "message", json_object_new_string(message->str)); g_string_free(message, TRUE); // If not BMSG then isn't a valid message return bmsg; } // NOTE: replaces \r with \0 to make it easier to parse static void sanitize_msg(gchar **msg) { while (*msg) { gchar *tmp = *msg++; if (g_str_has_suffix(tmp, "\r")) *strstr(tmp, "\r") = '\0'; } } json_object *bmessage_parse(const gchar *bmessage) { gchar **msg; json_object *jresp = json_object_new_object(); gboolean ret; if (!bmessage || !strlen(bmessage)) return NULL; AFB_INFO("Parsing incoming bMessage of length %lu", strlen(bmessage)); msg = g_strsplit(bmessage, "\n", -1); sanitize_msg(msg); ret = __bmessage_parse(msg, jresp); if (!ret) { json_object_put(jresp); jresp = NULL; } g_strfreev(msg); return jresp; }