freeswitch/libs/libblade/src/blade_web.c

675 lines
18 KiB
C

/*
* Copyright (c) 2017, Shane Bryldt
* 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 "blade.h"
struct blade_webrequest_s {
const char *action;
const char *path;
ks_hash_t *query;
ks_hash_t *headers;
ks_sb_t *content;
};
struct blade_webresponse_s {
const char *status_code;
const char *status_message;
ks_hash_t *headers;
ks_sb_t *content;
};
static void blade_webrequest_cleanup(void *ptr, void *arg, ks_pool_cleanup_action_t action, ks_pool_cleanup_type_t type)
{
blade_webrequest_t *bwreq = (blade_webrequest_t *)ptr;
ks_assert(bwreq);
switch (action) {
case KS_MPCL_ANNOUNCE:
break;
case KS_MPCL_TEARDOWN:
break;
case KS_MPCL_DESTROY:
break;
}
}
static void blade_webresponse_cleanup(void *ptr, void *arg, ks_pool_cleanup_action_t action, ks_pool_cleanup_type_t type)
{
blade_webresponse_t *bwres = (blade_webresponse_t *)ptr;
ks_assert(bwres);
switch (action) {
case KS_MPCL_ANNOUNCE:
break;
case KS_MPCL_TEARDOWN:
break;
case KS_MPCL_DESTROY:
break;
}
}
KS_DECLARE(ks_status_t) blade_webrequest_create(blade_webrequest_t **bwreqP, const char *action, const char *path)
{
ks_pool_t *pool = NULL;
blade_webrequest_t *bwreq = NULL;
ks_assert(bwreqP);
ks_assert(action);
ks_assert(path);
ks_pool_open(&pool);
ks_assert(pool);
bwreq = ks_pool_alloc(pool, sizeof(blade_webrequest_t));
bwreq->action = ks_pstrdup(pool, action);
bwreq->path = ks_pstrdup(pool, path);
ks_hash_create(&bwreq->query, KS_HASH_MODE_CASE_INSENSITIVE, KS_HASH_FLAG_NOLOCK | KS_HASH_FLAG_DUP_CHECK | KS_HASH_FLAG_FREE_KEY | KS_HASH_FLAG_FREE_VALUE, pool);
ks_assert(bwreq->query);
ks_hash_create(&bwreq->headers, KS_HASH_MODE_CASE_INSENSITIVE, KS_HASH_FLAG_NOLOCK | KS_HASH_FLAG_DUP_CHECK | KS_HASH_FLAG_FREE_KEY | KS_HASH_FLAG_FREE_VALUE, pool);
ks_assert(bwreq->headers);
ks_sb_create(&bwreq->content, pool, 0);
ks_assert(bwreq->content);
ks_pool_set_cleanup(bwreq, NULL, blade_webrequest_cleanup);
*bwreqP = bwreq;
blade_webrequest_header_add(bwreq, "Content-Type", "application/x-www-form-urlencoded");
return KS_STATUS_SUCCESS;
}
KS_DECLARE(ks_status_t) blade_webrequest_load(blade_webrequest_t **bwreqP, struct mg_connection *conn)
{
ks_status_t ret = KS_STATUS_SUCCESS;
ks_pool_t *pool = NULL;
blade_webrequest_t *bwreq = NULL;
const struct mg_request_info *info = NULL;
char buf[1024];
int bytes = 0;
ks_assert(bwreqP);
ks_assert(conn);
info = mg_get_request_info(conn);
ks_pool_open(&pool);
ks_assert(pool);
bwreq = ks_pool_alloc(pool, sizeof(blade_webrequest_t));
bwreq->action = ks_pstrdup(pool, info->request_method);
bwreq->path = ks_pstrdup(pool, info->request_uri);
ks_hash_create(&bwreq->query, KS_HASH_MODE_CASE_INSENSITIVE, KS_HASH_FLAG_NOLOCK | KS_HASH_FLAG_DUP_CHECK | KS_HASH_FLAG_FREE_KEY | KS_HASH_FLAG_FREE_VALUE, pool);
ks_assert(bwreq->query);
ks_hash_create(&bwreq->headers, KS_HASH_MODE_CASE_INSENSITIVE, KS_HASH_FLAG_NOLOCK | KS_HASH_FLAG_DUP_CHECK | KS_HASH_FLAG_FREE_KEY | KS_HASH_FLAG_FREE_VALUE, pool);
ks_assert(bwreq->headers);
ks_sb_create(&bwreq->content, pool, 0);
ks_assert(bwreq->content);
ks_pool_set_cleanup(bwreq, NULL, blade_webrequest_cleanup);
if (info->query_string && info->query_string[0]) {
char *query = ks_pstrdup(pool, info->query_string);
char *start = query;
char *end = NULL;
do {
char *key = start;
char *value = NULL;
end = strchr(start, '&');
if (end) *end = '\0';
value = strchr(start, '=');
if (value) {
*value = '\0';
value++;
if (*key && *value) {
ks_hash_insert(bwreq->query, (void *)ks_pstrdup(pool, key), (void *)ks_pstrdup(pool, value));
}
}
if (end) start = ++end;
else start = NULL;
} while (start);
ks_pool_free(&query);
}
for (int index = 0; index < info->num_headers; ++index) {
const struct mg_header *header = &info->http_headers[index];
ks_hash_insert(bwreq->headers, (void *)ks_pstrdup(pool, header->name), (void *)ks_pstrdup(pool, header->value));
}
while ((bytes = mg_read(conn, buf, sizeof(buf))) > 0) ks_sb_append_ex(bwreq->content, buf, bytes);
if (bytes < 0) {
blade_webrequest_destroy(&bwreq);
ret = KS_STATUS_FAIL;
}
else *bwreqP = bwreq;
return ret;
}
KS_DECLARE(ks_status_t) blade_webrequest_destroy(blade_webrequest_t **bwreqP)
{
blade_webrequest_t *bwreq = NULL;
ks_pool_t *pool;
ks_assert(bwreqP);
ks_assert(*bwreqP);
bwreq = *bwreqP;
*bwreqP = NULL;
pool = ks_pool_get(bwreq);
ks_pool_close(&pool);
return KS_STATUS_SUCCESS;
}
KS_DECLARE(const char *) blade_webrequest_action_get(blade_webrequest_t *bwreq)
{
ks_assert(bwreq);
return bwreq->action;
}
KS_DECLARE(const char *) blade_webrequest_path_get(blade_webrequest_t *bwreq)
{
ks_assert(bwreq);
return bwreq->path;
}
KS_DECLARE(ks_status_t) blade_webrequest_query_add(blade_webrequest_t *bwreq, const char *name, const char *value)
{
ks_assert(bwreq);
ks_assert(name);
ks_assert(value);
ks_hash_insert(bwreq->query, (void *)ks_pstrdup(ks_pool_get(bwreq), name), (void *)ks_pstrdup(ks_pool_get(bwreq), value));
return KS_STATUS_SUCCESS;
}
KS_DECLARE(const char *) blade_webrequest_query_get(blade_webrequest_t *bwreq, const char *name)
{
ks_assert(bwreq);
ks_assert(name);
return (const char *)ks_hash_search(bwreq->query, (void *)name, KS_UNLOCKED);
}
KS_DECLARE(ks_status_t) blade_webrequest_header_add(blade_webrequest_t *bwreq, const char *header, const char *value)
{
ks_assert(bwreq);
ks_assert(header);
ks_assert(value);
ks_hash_insert(bwreq->headers, (void *)ks_pstrdup(ks_pool_get(bwreq), header), (void *)ks_pstrdup(ks_pool_get(bwreq), value));
return KS_STATUS_SUCCESS;
}
KS_DECLARE(ks_status_t) blade_webrequest_header_printf(blade_webrequest_t *bwreq, const char *header, const char *fmt, ...)
{
va_list ap;
char *result = NULL;
ks_assert(bwreq);
ks_assert(header);
ks_assert(fmt);
va_start(ap, fmt);
result = ks_vpprintf(ks_pool_get(bwreq), fmt, ap);
va_end(ap);
ks_hash_insert(bwreq->headers, (void *)ks_pstrdup(ks_pool_get(bwreq), header), (void *)result);
return KS_STATUS_SUCCESS;
}
KS_DECLARE(const char *) blade_webrequest_header_get(blade_webrequest_t *bwreq, const char *header)
{
ks_assert(bwreq);
ks_assert(header);
return (const char *)ks_hash_search(bwreq->headers, (void *)header, KS_UNLOCKED);
}
KS_DECLARE(ks_status_t) blade_webrequest_content_json_append(blade_webrequest_t *bwreq, cJSON *json)
{
ks_assert(bwreq);
ks_assert(json);
blade_webrequest_header_add(bwreq, "Content-Type", "application/json");
ks_sb_json(bwreq->content, json);
return KS_STATUS_SUCCESS;
}
KS_DECLARE(ks_status_t) blade_webrequest_content_string_append(blade_webrequest_t *bwreq, const char *str)
{
ks_assert(bwreq);
ks_assert(str);
ks_sb_append(bwreq->content, str);
return KS_STATUS_SUCCESS;
}
KS_DECLARE(ks_status_t) blade_webrequest_send(blade_webrequest_t *bwreq, ks_bool_t secure, const char *host, ks_port_t port, blade_webresponse_t **bwresP)
{
ks_status_t ret = KS_STATUS_SUCCESS;
char buf[1024];
struct mg_connection *conn = NULL;
const char *path = NULL;
ks_sb_t *pathAndQuery = NULL;
ks_assert(bwreq);
ks_assert(host);
ks_assert(bwresP);
if (port == 0) port = secure ? 443 : 80;
conn = mg_connect_client(host, port, secure, buf, sizeof(buf));
if (!conn) {
ret = KS_STATUS_FAIL;
goto done;
}
path = bwreq->path;
if (ks_hash_count(bwreq->query) > 0) {
ks_bool_t firstQuery = KS_TRUE;
ks_sb_create(&pathAndQuery, NULL, 0);
ks_sb_append(pathAndQuery, bwreq->path);
for (ks_hash_iterator_t *it = ks_hash_first(bwreq->query, KS_UNLOCKED); it; it = ks_hash_next(&it)) {
const char *key;
const char *value;
ks_hash_this(it, (const void **)&key, NULL, (void **)&value);
// @todo make sure key and value are URL encoded
mg_url_encode(key, buf, sizeof(buf));
ks_sb_printf(pathAndQuery, "%c%s=", firstQuery ? '?' : '&', buf);
mg_url_encode(value, buf, sizeof(buf));
ks_sb_append(pathAndQuery, buf);
firstQuery = KS_FALSE;
}
path = ks_sb_cstr(pathAndQuery);
}
mg_printf(conn,
"%s %s HTTP/1.1\r\n"
"Host: %s\r\n"
"Content-Length: %lu\r\n",
bwreq->action,
path,
host,
ks_sb_length(bwreq->content));
if (pathAndQuery) ks_sb_destroy(&pathAndQuery);
for (ks_hash_iterator_t *it = ks_hash_first(bwreq->headers, KS_UNLOCKED); it; it = ks_hash_next(&it)) {
const char *key;
const char *value;
ks_hash_this(it, (const void **)&key, NULL, (void **)&value);
mg_printf(conn, "%s: %s\r\n", key, value);
}
mg_write(conn, "\r\n", 2);
mg_write(conn, ks_sb_cstr(bwreq->content), ks_sb_length(bwreq->content));
if (mg_get_response(conn, buf, sizeof(buf), 1000) <= 0) {
ret = KS_STATUS_FAIL;
goto done;
}
ret = blade_webresponse_load(bwresP, conn);
done:
if (conn) mg_close_connection(conn);
return ret;
}
KS_DECLARE(ks_status_t) blade_webrequest_oauth2_token_by_credentials_send(ks_bool_t secure, const char *host, ks_port_t port, const char *path, const char *client_id, const char *client_secret, const char **token)
{
ks_status_t ret = KS_STATUS_SUCCESS;
blade_webrequest_t *bwreq = NULL;
blade_webresponse_t *bwres = NULL;
cJSON *json = NULL;
char *auth = NULL;
char encoded[1024];
ks_pool_t *pool = NULL;
const char *tok = NULL;
ks_assert(host);
ks_assert(path);
ks_assert(client_id);
ks_assert(client_secret);
ks_assert(token);
blade_webrequest_create(&bwreq, "POST", path);
auth = ks_psprintf(ks_pool_get(bwreq), "%s:%s", client_id, client_secret);
ks_b64_encode((unsigned char *)auth, strlen(auth), (unsigned char *)encoded, sizeof(encoded));
ks_pool_free(&auth);
blade_webrequest_header_printf(bwreq, "Authorization", "Basic %s", encoded);
json = cJSON_CreateObject();
cJSON_AddStringToObject(json, "grant_type", "client_credentials");
blade_webrequest_content_json_append(bwreq, json);
cJSON_Delete(json);
if ((ret = blade_webrequest_send(bwreq, secure, host, port, &bwres)) != KS_STATUS_SUCCESS) goto done;
if ((ret = blade_webresponse_content_json_get(bwres, &json)) != KS_STATUS_SUCCESS) goto done;
if ((tok = cJSON_GetObjectCstr(json, "access_token")) == NULL) {
ret = KS_STATUS_FAIL;
goto done;
}
ks_pool_open(&pool);
*token = ks_pstrdup(pool, tok);
done:
if (json) cJSON_Delete(json);
blade_webrequest_destroy(&bwreq);
if (bwres) blade_webresponse_destroy(&bwres);
return ret;
}
KS_DECLARE(ks_status_t) blade_webrequest_oauth2_token_by_code_send(ks_bool_t secure, const char *host, ks_port_t port, const char *path, const char *client_id, const char *client_secret, const char *code, const char **token)
{
ks_status_t ret = KS_STATUS_SUCCESS;
blade_webrequest_t *bwreq = NULL;
blade_webresponse_t *bwres = NULL;
cJSON *json = NULL;
char *auth = NULL;
char encoded[1024];
ks_pool_t *pool = NULL;
const char *tok = NULL;
ks_assert(host);
ks_assert(path);
ks_assert(client_id);
ks_assert(client_secret);
ks_assert(code);
ks_assert(token);
blade_webrequest_create(&bwreq, "POST", path);
auth = ks_psprintf(ks_pool_get(bwreq), "%s:%s", client_id, client_secret);
ks_b64_encode((unsigned char *)auth, strlen(auth), (unsigned char *)encoded, sizeof(encoded));
ks_pool_free(&auth);
blade_webrequest_header_printf(bwreq, "Authorization", "Basic %s", encoded);
json = cJSON_CreateObject();
cJSON_AddStringToObject(json, "grant_type", "authorization_code");
cJSON_AddStringToObject(json, "code", code);
blade_webrequest_content_json_append(bwreq, json);
cJSON_Delete(json);
if ((ret = blade_webrequest_send(bwreq, secure, host, port, &bwres)) != KS_STATUS_SUCCESS) goto done;
if ((ret = blade_webresponse_content_json_get(bwres, &json)) != KS_STATUS_SUCCESS) goto done;
if ((tok = cJSON_GetObjectCstr(json, "access_token")) == NULL) {
ret = KS_STATUS_FAIL;
goto done;
}
ks_pool_open(&pool);
*token = ks_pstrdup(pool, tok);
done:
if (json) cJSON_Delete(json);
blade_webrequest_destroy(&bwreq);
if (bwres) blade_webresponse_destroy(&bwres);
return ret;
}
KS_DECLARE(ks_status_t) blade_webresponse_create(blade_webresponse_t **bwresP, const char *status)
{
ks_pool_t *pool = NULL;
blade_webresponse_t *bwres = NULL;
ks_assert(bwresP);
ks_assert(status);
ks_pool_open(&pool);
ks_assert(pool);
bwres = ks_pool_alloc(pool, sizeof(blade_webresponse_t));
bwres->status_code = ks_pstrdup(pool, status);
bwres->status_message = ks_pstrdup(pool, mg_get_response_code_text(NULL, atoi(status)));
ks_hash_create(&bwres->headers, KS_HASH_MODE_CASE_INSENSITIVE, KS_HASH_FLAG_NOLOCK | KS_HASH_FLAG_DUP_CHECK | KS_HASH_FLAG_FREE_KEY | KS_HASH_FLAG_FREE_VALUE, pool);
ks_assert(bwres->headers);
ks_sb_create(&bwres->content, pool, 0);
ks_assert(bwres->content);
ks_pool_set_cleanup(bwres, NULL, blade_webresponse_cleanup);
*bwresP = bwres;
blade_webresponse_header_add(bwres, "Content-Type", "application/x-www-form-urlencoded");
return KS_STATUS_SUCCESS;
}
KS_DECLARE(ks_status_t) blade_webresponse_load(blade_webresponse_t **bwresP, struct mg_connection *conn)
{
ks_status_t ret = KS_STATUS_SUCCESS;
ks_pool_t *pool = NULL;
blade_webresponse_t *bwres = NULL;
const struct mg_request_info *info = NULL;
char buf[1024];
int bytes = 0;
ks_assert(bwresP);
ks_assert(conn);
info = mg_get_request_info(conn);
ks_pool_open(&pool);
ks_assert(pool);
bwres = ks_pool_alloc(pool, sizeof(blade_webrequest_t));
bwres->status_code = ks_pstrdup(pool, info->request_uri);
bwres->status_message = ks_pstrdup(pool, info->http_version);
ks_hash_create(&bwres->headers, KS_HASH_MODE_CASE_INSENSITIVE, KS_HASH_FLAG_NOLOCK | KS_HASH_FLAG_DUP_CHECK | KS_HASH_FLAG_FREE_KEY | KS_HASH_FLAG_FREE_VALUE, pool);
ks_assert(bwres->headers);
ks_sb_create(&bwres->content, pool, 0);
ks_assert(bwres->content);
ks_pool_set_cleanup(bwres, NULL, blade_webresponse_cleanup);
for (int index = 0; index < info->num_headers; ++index) {
const struct mg_header *header = &info->http_headers[index];
ks_hash_insert(bwres->headers, (void *)ks_pstrdup(pool, header->name), (void *)ks_pstrdup(pool, header->value));
}
while ((bytes = mg_read(conn, buf, sizeof(buf))) > 0) ks_sb_append_ex(bwres->content, buf, bytes);
if (bytes < 0) {
blade_webresponse_destroy(&bwres);
ret = KS_STATUS_FAIL;
}
else *bwresP = bwres;
return ret;
}
KS_DECLARE(ks_status_t) blade_webresponse_destroy(blade_webresponse_t **bwresP)
{
blade_webresponse_t *bwres = NULL;
ks_pool_t *pool;
ks_assert(bwresP);
ks_assert(*bwresP);
bwres = *bwresP;
*bwresP = NULL;
pool = ks_pool_get(bwres);
ks_pool_close(&pool);
return KS_STATUS_SUCCESS;
}
KS_DECLARE(ks_status_t) blade_webresponse_header_add(blade_webresponse_t *bwres, const char *header, const char *value)
{
ks_assert(bwres);
ks_assert(header);
ks_assert(value);
ks_hash_insert(bwres->headers, (void *)ks_pstrdup(ks_pool_get(bwres), header), (void *)ks_pstrdup(ks_pool_get(bwres), value));
return KS_STATUS_SUCCESS;
}
KS_DECLARE(const char *) blade_webresponse_header_get(blade_webresponse_t *bwres, const char *header)
{
ks_assert(bwres);
ks_assert(header);
return (const char *)ks_hash_search(bwres->headers, (void *)header, KS_UNLOCKED);
}
KS_DECLARE(ks_status_t) blade_webresponse_content_json_append(blade_webresponse_t *bwres, cJSON *json)
{
ks_assert(bwres);
ks_assert(json);
blade_webresponse_header_add(bwres, "Content-Type", "application/json");
ks_sb_json(bwres->content, json);
return KS_STATUS_SUCCESS;
}
KS_DECLARE(ks_status_t) blade_webresponse_content_string_append(blade_webresponse_t *bwres, const char *str)
{
ks_assert(bwres);
ks_assert(str);
ks_sb_append(bwres->content, str);
return KS_STATUS_SUCCESS;
}
KS_DECLARE(ks_status_t) blade_webresponse_content_json_get(blade_webresponse_t *bwres, cJSON **json)
{
ks_status_t ret = KS_STATUS_SUCCESS;
ks_assert(bwres);
ks_assert(json);
if (!(*json = cJSON_Parse(ks_sb_cstr(bwres->content)))) ret = KS_STATUS_FAIL;
return ret;
}
KS_DECLARE(ks_status_t) blade_webresponse_send(blade_webresponse_t *bwres, struct mg_connection *conn)
{
ks_assert(bwres);
ks_assert(conn);
mg_printf(conn,
"HTTP/1.1 %s %s\r\n"
"Content-Length: %lu\r\n"
"Connection: close\r\n",
bwres->status_code,
bwres->status_message,
ks_sb_length(bwres->content));
for (ks_hash_iterator_t *it = ks_hash_first(bwres->headers, KS_UNLOCKED); it; it = ks_hash_next(&it)) {
const char *key;
const char *value;
ks_hash_this(it, (const void **)&key, NULL, (void **)&value);
mg_printf(conn, "%s: %s\r\n", key, value);
}
mg_write(conn, "\r\n", 2);
mg_write(conn, ks_sb_cstr(bwres->content), ks_sb_length(bwres->content));
return KS_STATUS_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:
*/