| /* This file is part of the CivetWeb web server. |
| * See https://github.com/civetweb/civetweb/ |
| * (C) 2015-2018 by the CivetWeb authors, MIT license. |
| */ |
| |
| #include "duktape.h" |
| |
| /* TODO: the mg context should be added to duktape as well */ |
| /* Alternative: redefine a new, clean API from scratch (instead of using mg), |
| * or at least do not add problematic functions. */ |
| /* For evaluation purposes, currently only "send" is supported. |
| * All other ~50 functions will be added later. */ |
| |
| /* Note: This is only experimental support, so the API may still change. */ |
| |
| static const char *civetweb_conn_id = "\xFF" |
| "civetweb_conn"; |
| static const char *civetweb_ctx_id = "\xFF" |
| "civetweb_ctx"; |
| |
| |
| static void * |
| mg_duk_mem_alloc(void *udata, duk_size_t size) |
| { |
| return mg_malloc_ctx(size, (struct mg_context *)udata); |
| } |
| |
| |
| static void * |
| mg_duk_mem_realloc(void *udata, void *ptr, duk_size_t newsize) |
| { |
| return mg_realloc_ctx(ptr, newsize, (struct mg_context *)udata); |
| } |
| |
| |
| static void |
| mg_duk_mem_free(void *udata, void *ptr) |
| { |
| (void)udata; |
| mg_free(ptr); |
| } |
| |
| static void |
| mg_duk_fatal_handler(duk_context *duk_ctx, duk_errcode_t code, const char *msg) |
| { |
| /* Script is called "protected" (duk_peval_file), so script errors should |
| * never yield in a call to this function. Maybe calls prior to executing |
| * the script could raise a fatal error. */ |
| struct mg_connection *conn; |
| |
| duk_push_global_stash(duk_ctx); |
| duk_get_prop_string(duk_ctx, -1, civetweb_conn_id); |
| conn = (struct mg_connection *)duk_to_pointer(duk_ctx, -1); |
| |
| mg_cry_internal(conn, "JavaScript fatal (%u): %s", (unsigned)code, msg); |
| } |
| |
| #if DUK_VERSION >= 20000L |
| /* Dropped from interface */ |
| duk_int_t duk_peval_file(duk_context *duk_ctx, const char *script); |
| |
| static void |
| mg_duk_v2_fatal(void *udata, const char *msg) |
| { |
| ; /* TODO: How to get "conn" without duk_ctx */ |
| } |
| |
| static void |
| push_file_as_string(duk_context *ctx, const char *filename) |
| { |
| FILE *f; |
| struct stat fst; |
| void *buf; |
| size_t len; |
| |
| if (0 != stat(filename, &fst)) { |
| duk_push_undefined(ctx); |
| return; |
| } |
| |
| f = fopen(filename, "rb"); |
| if (!f) { |
| duk_push_undefined(ctx); |
| return; |
| } |
| |
| buf = mg_malloc(fst.st_size); |
| if (!f) { |
| fclose(f); |
| duk_push_undefined(ctx); |
| return; |
| } |
| |
| len = fread(buf, 1, fst.st_size, f); |
| fclose(f); |
| |
| duk_push_lstring(ctx, (const char *)buf, (duk_size_t)len); |
| mg_free(buf); |
| } |
| |
| duk_int_t |
| duk_peval_file(duk_context *duk_ctx, const char *script) |
| { |
| push_file_as_string(duk_ctx, script); |
| return duk_peval(duk_ctx); |
| } |
| #endif |
| |
| static duk_ret_t |
| duk_itf_write(duk_context *duk_ctx) |
| { |
| struct mg_connection *conn; |
| duk_double_t ret; |
| duk_size_t len = 0; |
| const char *val = duk_require_lstring(duk_ctx, -1, &len); |
| |
| /* |
| duk_push_global_stash(duk_ctx); |
| duk_get_prop_string(duk_ctx, -1, civetweb_conn_id); |
| conn = (struct mg_connection *)duk_to_pointer(duk_ctx, -1); |
| */ |
| duk_push_current_function(duk_ctx); |
| duk_get_prop_string(duk_ctx, -1, civetweb_conn_id); |
| conn = (struct mg_connection *)duk_to_pointer(duk_ctx, -1); |
| |
| if (!conn) { |
| duk_error(duk_ctx, |
| DUK_ERR_ERROR, |
| "function not available without connection object"); |
| return DUK_RET_ERROR; |
| } |
| |
| ret = mg_write(conn, val, len); |
| |
| duk_push_number(duk_ctx, ret); |
| return 1; |
| } |
| |
| |
| static duk_ret_t |
| duk_itf_read(duk_context *duk_ctx) |
| { |
| struct mg_connection *conn; |
| char buf[1024]; |
| int len; |
| |
| duk_push_current_function(duk_ctx); |
| duk_get_prop_string(duk_ctx, -1, civetweb_conn_id); |
| conn = (struct mg_connection *)duk_to_pointer(duk_ctx, -1); |
| |
| if (!conn) { |
| duk_error(duk_ctx, |
| DUK_ERR_ERROR, |
| "function not available without connection object"); |
| return DUK_RET_ERROR; |
| } |
| |
| len = mg_read(conn, buf, sizeof(buf)); |
| |
| duk_push_lstring(duk_ctx, buf, len); |
| return 1; |
| } |
| |
| |
| static duk_ret_t |
| duk_itf_getoption(duk_context *duk_ctx) |
| { |
| struct mg_connection *conn; |
| const char *ret; |
| int optidx; |
| duk_size_t len = 0; |
| const char *val = duk_require_lstring(duk_ctx, -1, &len); |
| |
| duk_push_current_function(duk_ctx); |
| duk_get_prop_string(duk_ctx, -1, civetweb_conn_id); |
| conn = (struct mg_connection *)duk_to_pointer(duk_ctx, -1); |
| |
| if (!conn) { |
| duk_error(duk_ctx, |
| DUK_ERR_ERROR, |
| "function not available without context object"); |
| return DUK_RET_ERROR; |
| } |
| |
| optidx = get_option_index(val); |
| if (optidx >= 0) { |
| ret = conn->dom_ctx->config[optidx]; |
| } else { |
| ret = NULL; |
| } |
| if (ret) { |
| duk_push_string(duk_ctx, ret); |
| } else { |
| duk_push_null(duk_ctx); |
| } |
| |
| return 1; |
| } |
| |
| |
| static void |
| mg_exec_duktape_script(struct mg_connection *conn, const char *script_name) |
| { |
| int i; |
| duk_context *duk_ctx = NULL; |
| |
| conn->must_close = 1; |
| |
| /* Create Duktape interpreter state */ |
| duk_ctx = duk_create_heap(mg_duk_mem_alloc, |
| mg_duk_mem_realloc, |
| mg_duk_mem_free, |
| (void *)conn->phys_ctx, |
| #if DUK_VERSION >= 20000L |
| mg_duk_v2_fatal |
| #else |
| mg_duk_fatal_handler |
| #endif |
| ); |
| if (!duk_ctx) { |
| mg_cry_internal(conn, "%s", "Failed to create a Duktape heap."); |
| goto exec_duktape_finished; |
| } |
| |
| /* Add "conn" object */ |
| duk_push_global_object(duk_ctx); |
| duk_push_object(duk_ctx); /* create a new table/object ("conn") */ |
| |
| /* add function conn.write */ |
| duk_push_c_function(duk_ctx, duk_itf_write, 1 /* 1 = nargs */); |
| duk_push_pointer(duk_ctx, (void *)conn); |
| duk_put_prop_string(duk_ctx, -2, civetweb_conn_id); |
| duk_put_prop_string(duk_ctx, -2, "write"); |
| |
| /* add function conn.read */ |
| duk_push_c_function(duk_ctx, duk_itf_read, 0 /* 0 = nargs */); |
| duk_push_pointer(duk_ctx, (void *)conn); |
| duk_put_prop_string(duk_ctx, -2, civetweb_conn_id); |
| duk_put_prop_string(duk_ctx, -2, "read"); |
| |
| /* add request_method object */ |
| duk_push_string(duk_ctx, conn->request_info.request_method); |
| duk_put_prop_string(duk_ctx, |
| -2, |
| "request_method"); /* add string conn.r... */ |
| |
| duk_push_string(duk_ctx, conn->request_info.request_uri); |
| duk_put_prop_string(duk_ctx, -2, "request_uri"); |
| |
| duk_push_string(duk_ctx, conn->request_info.local_uri); |
| duk_put_prop_string(duk_ctx, -2, "uri"); |
| |
| duk_push_string(duk_ctx, conn->request_info.http_version); |
| duk_put_prop_string(duk_ctx, -2, "http_version"); |
| |
| duk_push_string(duk_ctx, conn->request_info.query_string); |
| duk_put_prop_string(duk_ctx, -2, "query_string"); |
| |
| duk_push_string(duk_ctx, conn->request_info.remote_addr); |
| duk_put_prop_string(duk_ctx, -2, "remote_addr"); |
| |
| duk_push_int(duk_ctx, conn->request_info.remote_port); |
| duk_put_prop_string(duk_ctx, -2, "remote_port"); |
| |
| duk_push_int(duk_ctx, ntohs(conn->client.lsa.sin.sin_port)); |
| duk_put_prop_string(duk_ctx, -2, "server_port"); |
| |
| duk_push_object(duk_ctx); /* subfolder "conn.http_headers" */ |
| for (i = 0; i < conn->request_info.num_headers; i++) { |
| duk_push_string(duk_ctx, conn->request_info.http_headers[i].value); |
| duk_put_prop_string(duk_ctx, |
| -2, |
| conn->request_info.http_headers[i].name); |
| } |
| duk_put_prop_string(duk_ctx, -2, "http_headers"); |
| |
| duk_put_prop_string(duk_ctx, -2, "conn"); /* call the table "conn" */ |
| |
| /* Add "civetweb" object */ |
| duk_push_global_object(duk_ctx); |
| duk_push_object(duk_ctx); /* create a new table/object ("conn") */ |
| |
| duk_push_string(duk_ctx, CIVETWEB_VERSION); |
| duk_put_prop_string(duk_ctx, -2, "version"); |
| |
| duk_push_string(duk_ctx, script_name); |
| duk_put_prop_string(duk_ctx, -2, "script_name"); |
| |
| /* add function civetweb.getoption */ |
| duk_push_c_function(duk_ctx, duk_itf_getoption, 1 /* 1 = nargs */); |
| duk_push_pointer(duk_ctx, (void *)conn); |
| duk_put_prop_string(duk_ctx, -2, civetweb_conn_id); |
| duk_put_prop_string(duk_ctx, -2, "getoption"); |
| |
| if (conn->phys_ctx != NULL) { |
| /* add system name */ |
| if (conn->phys_ctx->systemName != NULL) { |
| duk_push_string(duk_ctx, conn->phys_ctx->systemName); |
| duk_put_prop_string(duk_ctx, -2, "system"); |
| } |
| } |
| |
| duk_put_prop_string(duk_ctx, |
| -2, |
| "civetweb"); /* call the table "civetweb" */ |
| |
| duk_push_global_stash(duk_ctx); |
| duk_push_pointer(duk_ctx, (void *)conn); |
| duk_put_prop_string(duk_ctx, -2, civetweb_conn_id); |
| |
| if (duk_peval_file(duk_ctx, script_name) != 0) { |
| mg_cry_internal(conn, "%s", duk_safe_to_string(duk_ctx, -1)); |
| goto exec_duktape_finished; |
| } |
| duk_pop(duk_ctx); /* ignore result */ |
| |
| exec_duktape_finished: |
| duk_destroy_heap(duk_ctx); |
| } |
| |
| |
| /* End of mod_duktape.inl */ |