1/* This file is part of the CivetWeb web server.
2 * See https://github.com/civetweb/civetweb/
3 * (C) 2015-2018 by the CivetWeb authors, MIT license.
4 */
5
6#include "duktape.h"
7
8/* TODO: the mg context should be added to duktape as well */
9/* Alternative: redefine a new, clean API from scratch (instead of using mg),
10 * or at least do not add problematic functions. */
11/* For evaluation purposes, currently only "send" is supported.
12 * All other ~50 functions will be added later. */
13
14/* Note: This is only experimental support, so the API may still change. */
15
16static const char *const civetweb_conn_id = "\xFF"
17                                            "civetweb_conn";
18static const char *const civetweb_ctx_id = "\xFF"
19                                           "civetweb_ctx";
20
21
22static void *
23mg_duk_mem_alloc(void *udata, duk_size_t size)
24{
25	return mg_malloc_ctx(size, (struct mg_context *)udata);
26}
27
28
29static void *
30mg_duk_mem_realloc(void *udata, void *ptr, duk_size_t newsize)
31{
32	return mg_realloc_ctx(ptr, newsize, (struct mg_context *)udata);
33}
34
35
36static void
37mg_duk_mem_free(void *udata, void *ptr)
38{
39	(void)udata;
40	mg_free(ptr);
41}
42
43static void
44mg_duk_fatal_handler(duk_context *duk_ctx, duk_errcode_t code, const char *msg)
45{
46	/* Script is called "protected" (duk_peval_file), so script errors should
47	 * never yield in a call to this function. Maybe calls prior to executing
48	 * the script could raise a fatal error. */
49	struct mg_connection *conn;
50
51	duk_push_global_stash(duk_ctx);
52	duk_get_prop_string(duk_ctx, -1, civetweb_conn_id);
53	conn = (struct mg_connection *)duk_to_pointer(duk_ctx, -1);
54
55	mg_cry_internal(conn, "JavaScript fatal (%u): %s", (unsigned)code, msg);
56}
57
58#if DUK_VERSION >= 20000L
59/* Dropped from interface */
60duk_int_t duk_peval_file(duk_context *duk_ctx, const char *script);
61
62static void
63mg_duk_v2_fatal(void *udata, const char *msg)
64{
65	; /* TODO: How to get "conn" without duk_ctx */
66}
67
68static void
69push_file_as_string(duk_context *ctx, const char *filename)
70{
71	FILE *f;
72	struct stat fst;
73	void *buf;
74	size_t len;
75
76	if (0 != stat(filename, &fst)) {
77		duk_push_undefined(ctx);
78		return;
79	}
80
81	f = fopen(filename, "rb");
82	if (!f) {
83		duk_push_undefined(ctx);
84		return;
85	}
86
87	buf = mg_malloc(fst.st_size);
88	if (!f) {
89		fclose(f);
90		duk_push_undefined(ctx);
91		return;
92	}
93
94	len = fread(buf, 1, fst.st_size, f);
95	fclose(f);
96
97	duk_push_lstring(ctx, (const char *)buf, (duk_size_t)len);
98	mg_free(buf);
99}
100
101duk_int_t
102duk_peval_file(duk_context *duk_ctx, const char *script)
103{
104	push_file_as_string(duk_ctx, script);
105	return duk_peval(duk_ctx);
106}
107#endif
108
109static duk_ret_t
110duk_itf_write(duk_context *duk_ctx)
111{
112	struct mg_connection *conn;
113	duk_double_t ret;
114	duk_size_t len = 0;
115	const char *val = duk_require_lstring(duk_ctx, -1, &len);
116
117	/*
118	    duk_push_global_stash(duk_ctx);
119	    duk_get_prop_string(duk_ctx, -1, civetweb_conn_id);
120	    conn = (struct mg_connection *)duk_to_pointer(duk_ctx, -1);
121	*/
122	duk_push_current_function(duk_ctx);
123	duk_get_prop_string(duk_ctx, -1, civetweb_conn_id);
124	conn = (struct mg_connection *)duk_to_pointer(duk_ctx, -1);
125
126	if (!conn) {
127		duk_error(duk_ctx,
128		          DUK_ERR_ERROR,
129		          "function not available without connection object");
130		return DUK_RET_ERROR;
131	}
132
133	ret = mg_write(conn, val, len);
134
135	duk_push_number(duk_ctx, ret);
136	return 1;
137}
138
139
140static duk_ret_t
141duk_itf_read(duk_context *duk_ctx)
142{
143	struct mg_connection *conn;
144	char buf[1024];
145	int len;
146
147	duk_push_current_function(duk_ctx);
148	duk_get_prop_string(duk_ctx, -1, civetweb_conn_id);
149	conn = (struct mg_connection *)duk_to_pointer(duk_ctx, -1);
150
151	if (!conn) {
152		duk_error(duk_ctx,
153		          DUK_ERR_ERROR,
154		          "function not available without connection object");
155		return DUK_RET_ERROR;
156	}
157
158	len = mg_read(conn, buf, sizeof(buf));
159
160	duk_push_lstring(duk_ctx, buf, len);
161	return 1;
162}
163
164
165static duk_ret_t
166duk_itf_getoption(duk_context *duk_ctx)
167{
168	struct mg_connection *conn;
169	const char *ret;
170	int optidx;
171	duk_size_t len = 0;
172	const char *val = duk_require_lstring(duk_ctx, -1, &len);
173
174	duk_push_current_function(duk_ctx);
175	duk_get_prop_string(duk_ctx, -1, civetweb_conn_id);
176	conn = (struct mg_connection *)duk_to_pointer(duk_ctx, -1);
177
178	if (!conn) {
179		duk_error(duk_ctx,
180		          DUK_ERR_ERROR,
181		          "function not available without context object");
182		return DUK_RET_ERROR;
183	}
184
185	optidx = get_option_index(val);
186	if (optidx >= 0) {
187		ret = conn->dom_ctx->config[optidx];
188	} else {
189		ret = NULL;
190	}
191	if (ret) {
192		duk_push_string(duk_ctx, ret);
193	} else {
194		duk_push_null(duk_ctx);
195	}
196
197	return 1;
198}
199
200
201static void
202mg_exec_duktape_script(struct mg_connection *conn, const char *script_name)
203{
204	int i;
205	duk_context *duk_ctx = NULL;
206
207	conn->must_close = 1;
208
209	/* Create Duktape interpreter state */
210	duk_ctx = duk_create_heap(mg_duk_mem_alloc,
211	                          mg_duk_mem_realloc,
212	                          mg_duk_mem_free,
213	                          (void *)conn->phys_ctx,
214#if DUK_VERSION >= 20000L
215	                          mg_duk_v2_fatal
216#else
217	                          mg_duk_fatal_handler
218#endif
219	);
220	if (!duk_ctx) {
221		mg_cry_internal(conn, "%s", "Failed to create a Duktape heap.");
222		goto exec_duktape_finished;
223	}
224
225	/* Add "conn" object */
226	duk_push_global_object(duk_ctx);
227	duk_push_object(duk_ctx); /* create a new table/object ("conn") */
228
229	/* add function conn.write */
230	duk_push_c_function(duk_ctx, duk_itf_write, 1 /* 1 = nargs */);
231	duk_push_pointer(duk_ctx, (void *)conn);
232	duk_put_prop_string(duk_ctx, -2, civetweb_conn_id);
233	duk_put_prop_string(duk_ctx, -2, "write");
234
235	/* add function conn.read */
236	duk_push_c_function(duk_ctx, duk_itf_read, 0 /* 0 = nargs */);
237	duk_push_pointer(duk_ctx, (void *)conn);
238	duk_put_prop_string(duk_ctx, -2, civetweb_conn_id);
239	duk_put_prop_string(duk_ctx, -2, "read");
240
241	/* add request_method object */
242	duk_push_string(duk_ctx, conn->request_info.request_method);
243	duk_put_prop_string(duk_ctx,
244	                    -2,
245	                    "request_method"); /* add string conn.r... */
246
247	duk_push_string(duk_ctx, conn->request_info.request_uri);
248	duk_put_prop_string(duk_ctx, -2, "request_uri");
249
250	duk_push_string(duk_ctx, conn->request_info.local_uri);
251	duk_put_prop_string(duk_ctx, -2, "uri");
252
253	duk_push_string(duk_ctx, conn->request_info.http_version);
254	duk_put_prop_string(duk_ctx, -2, "http_version");
255
256	duk_push_string(duk_ctx, conn->request_info.query_string);
257	duk_put_prop_string(duk_ctx, -2, "query_string");
258
259	duk_push_string(duk_ctx, conn->request_info.remote_addr);
260	duk_put_prop_string(duk_ctx, -2, "remote_addr");
261
262	duk_push_int(duk_ctx, conn->request_info.remote_port);
263	duk_put_prop_string(duk_ctx, -2, "remote_port");
264
265	duk_push_int(duk_ctx, ntohs(conn->client.lsa.sin.sin_port));
266	duk_put_prop_string(duk_ctx, -2, "server_port");
267
268	duk_push_object(duk_ctx); /* subfolder "conn.http_headers" */
269	for (i = 0; i < conn->request_info.num_headers; i++) {
270		duk_push_string(duk_ctx, conn->request_info.http_headers[i].value);
271		duk_put_prop_string(duk_ctx,
272		                    -2,
273		                    conn->request_info.http_headers[i].name);
274	}
275	duk_put_prop_string(duk_ctx, -2, "http_headers");
276
277	duk_put_prop_string(duk_ctx, -2, "conn"); /* call the table "conn" */
278
279	/* Add "civetweb" object */
280	duk_push_global_object(duk_ctx);
281	duk_push_object(duk_ctx); /* create a new table/object ("conn") */
282
283	duk_push_string(duk_ctx, CIVETWEB_VERSION);
284	duk_put_prop_string(duk_ctx, -2, "version");
285
286	duk_push_string(duk_ctx, script_name);
287	duk_put_prop_string(duk_ctx, -2, "script_name");
288
289	/* add function civetweb.getoption */
290	duk_push_c_function(duk_ctx, duk_itf_getoption, 1 /* 1 = nargs */);
291	duk_push_pointer(duk_ctx, (void *)conn);
292	duk_put_prop_string(duk_ctx, -2, civetweb_conn_id);
293	duk_put_prop_string(duk_ctx, -2, "getoption");
294
295	if (conn->phys_ctx != NULL) {
296		/* add system name */
297		if (conn->phys_ctx->systemName != NULL) {
298			duk_push_string(duk_ctx, conn->phys_ctx->systemName);
299			duk_put_prop_string(duk_ctx, -2, "system");
300		}
301	}
302
303	duk_put_prop_string(duk_ctx,
304	                    -2,
305	                    "civetweb"); /* call the table "civetweb" */
306
307	duk_push_global_stash(duk_ctx);
308	duk_push_pointer(duk_ctx, (void *)conn);
309	duk_put_prop_string(duk_ctx, -2, civetweb_conn_id);
310
311	if (duk_peval_file(duk_ctx, script_name) != 0) {
312		mg_cry_internal(conn, "%s", duk_safe_to_string(duk_ctx, -1));
313		goto exec_duktape_finished;
314	}
315	duk_pop(duk_ctx); /* ignore result */
316
317exec_duktape_finished:
318	duk_destroy_heap(duk_ctx);
319}
320
321
322/* End of mod_duktape.inl */
323