/* * Copyright (c) 2007-2014, Anthony Minessale II * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of the original author; nor the names of any contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include static char *my_dup(const char *s) { size_t len = strlen(s) + 1; void *new = malloc(len); esl_assert(new); return (char *) memcpy(new, s, len); } #ifndef ALLOC #define ALLOC(size) malloc(size) #endif #ifndef DUP #define DUP(str) my_dup(str) #endif #ifndef FREE #define FREE(ptr) esl_safe_free(ptr) #endif /* make sure this is synced with the esl_event_types_t enum in esl_types.h also never put any new ones before EVENT_ALL */ static const char *EVENT_NAMES[] = { "CUSTOM", "CLONE", "CHANNEL_CREATE", "CHANNEL_DESTROY", "CHANNEL_STATE", "CHANNEL_CALLSTATE", "CHANNEL_ANSWER", "CHANNEL_HANGUP", "CHANNEL_HANGUP_COMPLETE", "CHANNEL_EXECUTE", "CHANNEL_EXECUTE_COMPLETE", "CHANNEL_HOLD", "CHANNEL_UNHOLD", "CHANNEL_BRIDGE", "CHANNEL_UNBRIDGE", "CHANNEL_PROGRESS", "CHANNEL_PROGRESS_MEDIA", "CHANNEL_OUTGOING", "CHANNEL_PARK", "CHANNEL_UNPARK", "CHANNEL_APPLICATION", "CHANNEL_ORIGINATE", "CHANNEL_UUID", "API", "LOG", "INBOUND_CHAN", "OUTBOUND_CHAN", "STARTUP", "SHUTDOWN", "PUBLISH", "UNPUBLISH", "TALK", "NOTALK", "SESSION_CRASH", "MODULE_LOAD", "MODULE_UNLOAD", "DTMF", "MESSAGE", "PRESENCE_IN", "NOTIFY_IN", "PRESENCE_OUT", "PRESENCE_PROBE", "MESSAGE_WAITING", "MESSAGE_QUERY", "ROSTER", "CODEC", "BACKGROUND_JOB", "DETECTED_SPEECH", "DETECTED_TONE", "PRIVATE_COMMAND", "HEARTBEAT", "TRAP", "ADD_SCHEDULE", "DEL_SCHEDULE", "EXE_SCHEDULE", "RE_SCHEDULE", "RELOADXML", "NOTIFY", "PHONE_FEATURE", "PHONE_FEATURE_SUBSCRIBE", "SEND_MESSAGE", "RECV_MESSAGE", "REQUEST_PARAMS", "CHANNEL_DATA", "GENERAL", "COMMAND", "SESSION_HEARTBEAT", "CLIENT_DISCONNECTED", "SERVER_DISCONNECTED", "SEND_INFO", "RECV_INFO", "RECV_RTCP_MESSAGE", "CALL_SECURE", "NAT", "RECORD_START", "RECORD_STOP", "PLAYBACK_START", "PLAYBACK_STOP", "CALL_UPDATE", "FAILURE", "SOCKET_DATA", "MEDIA_BUG_START", "MEDIA_BUG_STOP", "CONFERENCE_DATA_QUERY", "CONFERENCE_DATA", "CALL_SETUP_REQ", "CALL_SETUP_RESULT", "CALL_DETAIL", "DEVICE_STATE", "ALL" }; ESL_DECLARE(const char *)esl_event_name(esl_event_types_t event) { return EVENT_NAMES[event]; } ESL_DECLARE(esl_status_t) esl_name_event(const char *name, esl_event_types_t *type) { esl_event_types_t x; for (x = 0; x <= ESL_EVENT_ALL; x++) { if ((strlen(name) > 13 && !strcasecmp(name + 13, EVENT_NAMES[x])) || !strcasecmp(name, EVENT_NAMES[x])) { *type = x; return ESL_SUCCESS; } } return ESL_FAIL; } ESL_DECLARE(esl_status_t) esl_event_create_subclass(esl_event_t **event, esl_event_types_t event_id, const char *subclass_name) { *event = NULL; if ((event_id != ESL_EVENT_CLONE && event_id != ESL_EVENT_CUSTOM) && subclass_name) { return ESL_FAIL; } *event = ALLOC(sizeof(esl_event_t)); esl_assert(*event); memset(*event, 0, sizeof(esl_event_t)); if (event_id != ESL_EVENT_CLONE) { (*event)->event_id = event_id; esl_event_add_header_string(*event, ESL_STACK_BOTTOM, "Event-Name", esl_event_name((*event)->event_id)); } if (subclass_name) { (*event)->subclass_name = DUP(subclass_name); esl_event_add_header_string(*event, ESL_STACK_BOTTOM, "Event-Subclass", subclass_name); } return ESL_SUCCESS; } ESL_DECLARE(const char *)esl_priority_name(esl_priority_t priority) { switch (priority) { /*lol */ case ESL_PRIORITY_NORMAL: return "NORMAL"; case ESL_PRIORITY_LOW: return "LOW"; case ESL_PRIORITY_HIGH: return "HIGH"; default: return "INVALID"; } } ESL_DECLARE(esl_status_t) esl_event_set_priority(esl_event_t *event, esl_priority_t priority) { event->priority = priority; esl_event_add_header_string(event, ESL_STACK_TOP, "priority", esl_priority_name(priority)); return ESL_SUCCESS; } #define ESL_HASH_KEY_STRING -1 static unsigned int esl_ci_hashfunc_default(const char *char_key, esl_ssize_t *klen) { unsigned int hash = 0; const unsigned char *key = (const unsigned char *)char_key; const unsigned char *p; esl_ssize_t i; if (*klen == ESL_HASH_KEY_STRING) { for (p = key; *p; p++) { hash = hash * 33 + tolower(*p); } *klen = p - key; } else { for (p = key, i = *klen; i; i--, p++) { hash = hash * 33 + tolower(*p); } } return hash; } ESL_DECLARE(esl_event_header_t *) esl_event_get_header_ptr(esl_event_t *event, const char *header_name) { esl_event_header_t *hp; esl_ssize_t hlen = -1; unsigned long hash = 0; esl_assert(event); if (!header_name) return NULL; hash = esl_ci_hashfunc_default(header_name, &hlen); for (hp = event->headers; hp; hp = hp->next) { if ((!hp->hash || hash == hp->hash) && !strcasecmp(hp->name, header_name)) { return hp; } } return NULL; } ESL_DECLARE(char *) esl_event_get_header_idx(esl_event_t *event, const char *header_name, int idx) { esl_event_header_t *hp; if ((hp = esl_event_get_header_ptr(event, header_name))) { if (idx > -1) { if (idx < hp->idx) { return hp->array[idx]; } else { return NULL; } } return hp->value; } else if (header_name && !strcmp(header_name, "_body")) { return event->body; } return NULL; } ESL_DECLARE(char *)esl_event_get_body(esl_event_t *event) { return (event ? event->body : NULL); } ESL_DECLARE(esl_status_t) esl_event_del_header_val(esl_event_t *event, const char *header_name, const char *val) { esl_event_header_t *hp, *lp = NULL, *tp; esl_status_t status = (esl_status_t) ESL_FALSE; int x = 0; esl_ssize_t hlen = -1; unsigned long hash = 0; tp = event->headers; while (tp) { hp = tp; tp = tp->next; x++; esl_assert(x < 1000000); hash = esl_ci_hashfunc_default(header_name, &hlen); if ((!hp->hash || hash == hp->hash) && (hp->name && !strcasecmp(header_name, hp->name)) && (esl_strlen_zero(val) || (hp->value && !strcmp(hp->value, val)))) { if (lp) { lp->next = hp->next; } else { event->headers = hp->next; } if (hp == event->last_header || !hp->next) { event->last_header = lp; } FREE(hp->name); if (hp->idx) { int i = 0; for (i = 0; i < hp->idx; i++) { FREE(hp->array[i]); } FREE(hp->array); } FREE(hp->value); memset(hp, 0, sizeof(*hp)); #ifdef ESL_EVENT_RECYCLE if (esl_queue_trypush(EVENT_HEADER_RECYCLE_QUEUE, hp) != ESL_SUCCESS) { FREE(hp); } #else FREE(hp); #endif status = ESL_SUCCESS; } else { lp = hp; } } return status; } static esl_event_header_t *new_header(const char *header_name) { esl_event_header_t *header; #ifdef ESL_EVENT_RECYCLE void *pop; if (esl_queue_trypop(EVENT_HEADER_RECYCLE_QUEUE, &pop) == ESL_SUCCESS) { header = (esl_event_header_t *) pop; } else { #endif header = ALLOC(sizeof(*header)); esl_assert(header); #ifdef ESL_EVENT_RECYCLE } #endif memset(header, 0, sizeof(*header)); header->name = DUP(header_name); return header; } ESL_DECLARE(int) esl_event_add_array(esl_event_t *event, const char *var, const char *val) { char *data; char **array; int max = 0; int len; const char *p; int i; if (strlen(val) < 8) { return -1; } p = val + 7; max = 1; while((p = strstr(p, "|:"))) { max++; p += 2; } data = strdup(val + 7); len = (sizeof(char *) * max) + 1; array = malloc(len); esl_assert(array); memset(array, 0, len); esl_separate_string_string(data, "|:", array, max); for(i = 0; i < max; i++) { esl_event_add_header_string(event, ESL_STACK_PUSH, var, array[i]); } free(array); free(data); return 0; } static esl_status_t esl_event_base_add_header(esl_event_t *event, esl_stack_t stack, const char *header_name, char *data) { esl_event_header_t *header = NULL; esl_ssize_t hlen = -1; int exists = 0, fly = 0; char *index_ptr; int index = 0; char *real_header_name = NULL; if (!strcmp(header_name, "_body")) { esl_event_set_body(event, data); } if ((index_ptr = strchr(header_name, '['))) { index_ptr++; index = atoi(index_ptr); real_header_name = DUP(header_name); if ((index_ptr = strchr(real_header_name, '['))) { *index_ptr++ = '\0'; } header_name = real_header_name; } if (index_ptr || (stack & ESL_STACK_PUSH) || (stack & ESL_STACK_UNSHIFT)) { if (!(header = esl_event_get_header_ptr(event, header_name)) && index_ptr) { header = new_header(header_name); if (esl_test_flag(event, ESL_EF_UNIQ_HEADERS)) { esl_event_del_header(event, header_name); } fly++; } if (header || (header = esl_event_get_header_ptr(event, header_name))) { if (index_ptr) { if (index > -1 && index <= 4000) { if (index < header->idx) { FREE(header->array[index]); header->array[index] = DUP(data); } else { int i; char **m; m = realloc(header->array, sizeof(char *) * (index + 1)); esl_assert(m); header->array = m; for (i = header->idx; i < index; i++) { m[i] = DUP(""); } m[index] = DUP(data); header->idx = index + 1; if (!fly) { exists = 1; } goto redraw; } } goto end; } else { if ((stack & ESL_STACK_PUSH) || (stack & ESL_STACK_UNSHIFT)) { exists++; stack &= ~(ESL_STACK_TOP | ESL_STACK_BOTTOM); } else { header = NULL; } } } } if (!header) { if (esl_strlen_zero(data)) { esl_event_del_header(event, header_name); FREE(data); goto end; } if (esl_test_flag(event, ESL_EF_UNIQ_HEADERS)) { esl_event_del_header(event, header_name); } if (strstr(data, "ARRAY::")) { esl_event_add_array(event, header_name, data); FREE(data); goto end; } header = new_header(header_name); } if ((stack & ESL_STACK_PUSH) || (stack & ESL_STACK_UNSHIFT)) { char **m = NULL; esl_size_t len = 0; char *hv; int i = 0, j = 0; if (header->value && !header->idx) { m = malloc(sizeof(char *)); esl_assert(m); m[0] = header->value; header->value = NULL; header->array = m; header->idx++; m = NULL; } i = header->idx + 1; m = realloc(header->array, sizeof(char *) * i); esl_assert(m); if ((stack & ESL_STACK_PUSH)) { m[header->idx] = data; } else if ((stack & ESL_STACK_UNSHIFT)) { for (j = header->idx; j > 0; j--) { m[j] = m[j-1]; } m[0] = data; } header->idx++; header->array = m; redraw: len = 0; for(j = 0; j < header->idx; j++) { len += strlen(header->array[j]) + 2; } if (len) { len += 8; hv = realloc(header->value, len); esl_assert(hv); header->value = hv; if (header->idx > 1) { esl_snprintf(header->value, len, "ARRAY::"); } else { *header->value = '\0'; } for(j = 0; j < header->idx; j++) { esl_snprintf(header->value + strlen(header->value), len - strlen(header->value), "%s%s", j == 0 ? "" : "|:", header->array[j]); } } } else { header->value = data; } if (!exists) { header->hash = esl_ci_hashfunc_default(header->name, &hlen); if ((stack & ESL_STACK_TOP)) { header->next = event->headers; event->headers = header; if (!event->last_header) { event->last_header = header; } } else { if (event->last_header) { event->last_header->next = header; } else { event->headers = header; header->next = NULL; } event->last_header = header; } } end: esl_safe_free(real_header_name); return ESL_SUCCESS; } ESL_DECLARE(esl_status_t) esl_event_add_header(esl_event_t *event, esl_stack_t stack, const char *header_name, const char *fmt, ...) { int ret = 0; char *data; va_list ap; va_start(ap, fmt); ret = esl_vasprintf(&data, fmt, ap); va_end(ap); if (ret == -1) { return ESL_FAIL; } return esl_event_base_add_header(event, stack, header_name, data); } ESL_DECLARE(esl_status_t) esl_event_add_header_string(esl_event_t *event, esl_stack_t stack, const char *header_name, const char *data) { if (data) { return esl_event_base_add_header(event, stack, header_name, DUP(data)); } return ESL_FAIL; } ESL_DECLARE(esl_status_t) esl_event_set_body(esl_event_t *event, const char *body) { esl_safe_free(event->body); if (body) { event->body = DUP(body); } return ESL_SUCCESS; } ESL_DECLARE(esl_status_t) esl_event_add_body(esl_event_t *event, const char *fmt, ...) { int ret = 0; char *data; va_list ap; if (fmt) { va_start(ap, fmt); ret = esl_vasprintf(&data, fmt, ap); va_end(ap); if (ret == -1) { return ESL_FAIL; } else { esl_safe_free(event->body); event->body = data; return ESL_SUCCESS; } } else { return ESL_FAIL; } } ESL_DECLARE(void) esl_event_destroy(esl_event_t **event) { esl_event_t *ep = *event; esl_event_header_t *hp, *this; if (ep) { for (hp = ep->headers; hp;) { this = hp; hp = hp->next; FREE(this->name); if (this->idx) { int i = 0; for (i = 0; i < this->idx; i++) { FREE(this->array[i]); } FREE(this->array); } FREE(this->value); #ifdef ESL_EVENT_RECYCLE if (esl_queue_trypush(EVENT_HEADER_RECYCLE_QUEUE, this) != ESL_SUCCESS) { FREE(this); } #else FREE(this); #endif } FREE(ep->body); FREE(ep->subclass_name); #ifdef ESL_EVENT_RECYCLE if (esl_queue_trypush(EVENT_RECYCLE_QUEUE, ep) != ESL_SUCCESS) { FREE(ep); } #else FREE(ep); #endif } *event = NULL; } ESL_DECLARE(void) esl_event_merge(esl_event_t *event, esl_event_t *tomerge) { esl_event_header_t *hp; esl_assert(tomerge && event); for (hp = tomerge->headers; hp; hp = hp->next) { if (hp->idx) { int i; for(i = 0; i < hp->idx; i++) { esl_event_add_header_string(event, ESL_STACK_PUSH, hp->name, hp->array[i]); } } else { esl_event_add_header_string(event, ESL_STACK_BOTTOM, hp->name, hp->value); } } } ESL_DECLARE(esl_status_t) esl_event_dup(esl_event_t **event, esl_event_t *todup) { esl_event_header_t *hp; if (esl_event_create_subclass(event, ESL_EVENT_CLONE, todup->subclass_name) != ESL_SUCCESS) { return ESL_GENERR; } (*event)->event_id = todup->event_id; (*event)->event_user_data = todup->event_user_data; (*event)->bind_user_data = todup->bind_user_data; (*event)->flags = todup->flags; for (hp = todup->headers; hp; hp = hp->next) { if (todup->subclass_name && !strcmp(hp->name, "Event-Subclass")) { continue; } if (hp->idx) { int i; for (i = 0; i < hp->idx; i++) { esl_event_add_header_string(*event, ESL_STACK_PUSH, hp->name, hp->array[i]); } } else { esl_event_add_header_string(*event, ESL_STACK_BOTTOM, hp->name, hp->value); } } if (todup->body) { (*event)->body = DUP(todup->body); } (*event)->key = todup->key; return ESL_SUCCESS; } ESL_DECLARE(esl_status_t) esl_event_serialize(esl_event_t *event, char **str, esl_bool_t encode) { esl_size_t len = 0; esl_event_header_t *hp; esl_size_t llen = 0, dlen = 0, blocksize = 512, encode_len = 1536, new_len = 0; char *buf; char *encode_buf = NULL; /* used for url encoding of variables to make sure unsafe things stay out of the serialized copy */ *str = NULL; dlen = blocksize * 2; if (!(buf = malloc(dlen))) { abort(); } /* go ahead and give ourselves some space to work with, should save a few reallocs */ if (!(encode_buf = malloc(encode_len))) { abort(); } /* esl_log_printf(ESL_CHANNEL_LOG, ESL_LOG_INFO, "hit serialized!.\n"); */ for (hp = event->headers; hp; hp = hp->next) { /* * grab enough memory to store 3x the string (url encode takes one char and turns it into %XX) * so we could end up with a string that is 3 times the originals length, unlikely but rather * be safe than destroy the string, also add one for the null. And try to be smart about using * the memory, allocate and only reallocate if we need more. This avoids an alloc, free CPU * destroying loop. */ if (hp->idx) { int i; new_len = 0; for(i = 0; i < hp->idx; i++) { new_len += (strlen(hp->array[i]) * 3) + 1; } } else { new_len = (strlen(hp->value) * 3) + 1; } if (encode_len < new_len) { char *tmp; /* keep track of the size of our allocation */ encode_len = new_len; if (!(tmp = realloc(encode_buf, encode_len))) { abort(); } encode_buf = tmp; } /* handle any bad things in the string like newlines : etc that screw up the serialized format */ if (encode) { esl_url_encode(hp->value, encode_buf, encode_len); } else { esl_snprintf(encode_buf, encode_len, "%s", hp->value); } llen = strlen(hp->name) + strlen(encode_buf) + 8; if ((len + llen) > dlen) { char *m; char *old = buf; dlen += (blocksize + (len + llen)); if ((m = realloc(buf, dlen))) { buf = m; } else { buf = old; abort(); } } esl_snprintf(buf + len, dlen - len, "%s: %s\n", hp->name, *encode_buf == '\0' ? "_undef_" : encode_buf); len = strlen(buf); } /* we are done with the memory we used for encoding, give it back */ esl_safe_free(encode_buf); if (event->body) { int blen = (int) strlen(event->body); llen = blen; if (blen) { llen += 25; } else { llen += 5; } if ((len + llen) > dlen) { char *m; char *old = buf; dlen += (blocksize + (len + llen)); if ((m = realloc(buf, dlen))) { buf = m; } else { buf = old; abort(); } } if (blen) { esl_snprintf(buf + len, dlen - len, "Content-Length: %d\n\n%s", blen, event->body); } else { esl_snprintf(buf + len, dlen - len, "\n"); } } else { esl_snprintf(buf + len, dlen - len, "\n"); } *str = buf; return ESL_SUCCESS; } ESL_DECLARE(esl_status_t) esl_event_create_json(esl_event_t **event, const char *json) { esl_event_t *new_event; cJSON *cj, *cjp; if (!(cj = cJSON_Parse(json))) { return (esl_status_t) ESL_FALSE; } if (esl_event_create(&new_event, ESL_EVENT_CLONE) != ESL_SUCCESS) { cJSON_Delete(cj); return (esl_status_t) ESL_FALSE; } for (cjp = cj->child; cjp; cjp = cjp->next) { char *name = cjp->string; char *value = cjp->valuestring; if (name && value) { if (!strcasecmp(name, "_body")) { esl_event_add_body(new_event, value, ESL_VA_NONE); } else { if (!strcasecmp(name, "event-name")) { esl_event_del_header(new_event, "event-name"); esl_name_event(value, &new_event->event_id); } esl_event_add_header_string(new_event, ESL_STACK_BOTTOM, name, value); } } else if (name) { if (cjp->type == cJSON_Array) { int i, x = cJSON_GetArraySize(cjp); for (i = 0; i < x; i++) { cJSON *item = cJSON_GetArrayItem(cjp, i); if (item != NULL && item->type == cJSON_String && item->valuestring) { esl_event_add_header_string(new_event, ESL_STACK_PUSH, name, item->valuestring); } } } } } cJSON_Delete(cj); *event = new_event; return ESL_SUCCESS; } ESL_DECLARE(esl_status_t) esl_event_serialize_json(esl_event_t *event, char **str) { esl_event_header_t *hp; cJSON *cj; *str = NULL; cj = cJSON_CreateObject(); for (hp = event->headers; hp; hp = hp->next) { if (hp->idx) { cJSON *a = cJSON_CreateArray(); int i; for(i = 0; i < hp->idx; i++) { cJSON_AddItemToArray(a, cJSON_CreateString(hp->array[i])); } cJSON_AddItemToObject(cj, hp->name, a); } else { cJSON_AddItemToObject(cj, hp->name, cJSON_CreateString(hp->value)); } } if (event->body) { int blen = (int) strlen(event->body); char tmp[25]; esl_snprintf(tmp, sizeof(tmp), "%d", blen); cJSON_AddItemToObject(cj, "Content-Length", cJSON_CreateString(tmp)); cJSON_AddItemToObject(cj, "_body", cJSON_CreateString(event->body)); } *str = cJSON_Print(cj); cJSON_Delete(cj); return ESL_SUCCESS; } /* For Emacs: * Local Variables: * mode:c * indent-tabs-mode:t * tab-width:4 * c-basic-offset:4 * End: * For VIM: * vim:set softtabstop=4 shiftwidth=4 tabstop=4 noet: */